From d2c989984610982056cc02dc6898cf23ad2fd3f5 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 13:16:48 -0500 Subject: [PATCH 01/11] minor update to element count report and a utility for getting integration test data root --- applications/utils/CMakeLists.txt | 2 +- .../wmtk/applications/utils/CMakeLists.txt | 4 ++ .../utils/element_count_report.cpp | 45 +++++++++++-------- .../utils/element_count_report.hpp | 5 +++ .../utils/get_integration_test_data_root.cpp | 14 ++++++ .../utils/get_integration_test_data_root.hpp | 6 +++ 6 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.cpp create mode 100644 applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp diff --git a/applications/utils/CMakeLists.txt b/applications/utils/CMakeLists.txt index 32d8efb03c..8d35070839 100644 --- a/applications/utils/CMakeLists.txt +++ b/applications/utils/CMakeLists.txt @@ -4,7 +4,7 @@ add_library(wmtk_application_utils) add_subdirectory(src/wmtk/applications/utils) -target_link_libraries(wmtk_application_utils wildmeshing_toolkit nlohmann_json) +target_link_libraries(wmtk_application_utils wildmeshing_toolkit nlohmann_json wmtk::multimesh wmtk::output) target_include_directories(wmtk_application_utils INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src") diff --git a/applications/utils/src/wmtk/applications/utils/CMakeLists.txt b/applications/utils/src/wmtk/applications/utils/CMakeLists.txt index 1a3d4887e2..69c9af9be6 100644 --- a/applications/utils/src/wmtk/applications/utils/CMakeLists.txt +++ b/applications/utils/src/wmtk/applications/utils/CMakeLists.txt @@ -5,4 +5,8 @@ target_sources(wmtk_application_utils PRIVATE parse_jse.hpp parse_jse.cpp + get_integration_test_data_root.hpp + get_integration_test_data_root.cpp + + ) diff --git a/applications/utils/src/wmtk/applications/utils/element_count_report.cpp b/applications/utils/src/wmtk/applications/utils/element_count_report.cpp index e3b4b4fa22..7b9bf8eac6 100644 --- a/applications/utils/src/wmtk/applications/utils/element_count_report.cpp +++ b/applications/utils/src/wmtk/applications/utils/element_count_report.cpp @@ -1,31 +1,40 @@ #include "element_count_report.hpp" #include +#include #include namespace wmtk::applications::utils { - std::vector element_count_report(const Mesh& m) { - std::vector ret; - for(PrimitiveType pt:wmtk::utils::primitive_below(m.top_simplex_type(), /*lower_to_upper=*/true) ){ - - ret.emplace_back(m.get_all(pt).size()); - - } - return ret; +std::vector element_count_report(const Mesh& m) +{ + std::vector ret; + for (PrimitiveType pt : + wmtk::utils::primitive_below(m.top_simplex_type(), /*lower_to_upper=*/true)) { + ret.emplace_back(m.get_all(pt).size()); } - nlohmann::json element_count_report_named(const Mesh& m) { - const static std::array names{"vertices","edges","faces","cells"}; - - const auto sizes = element_count_report(m); + return ret; +} +nlohmann::json element_count_report_named(const Mesh& m) +{ + const static std::array names{"vertices", "edges", "faces", "cells"}; - assert(sizes.size() <= names.size()); - nlohmann::json js; - for(size_t i = 0; i < sizes.size(); ++i) { - js[names[i]] = sizes[i]; + const auto sizes = element_count_report(m); - } - return js; + assert(sizes.size() <= names.size()); + nlohmann::json js; + for (size_t i = 0; i < sizes.size(); ++i) { + js[names[i]] = sizes[i]; } + return js; +} +nlohmann::json element_count_report_named(const components::multimesh::MeshCollection& meshes) +{ + nlohmann::json out; + for (const auto& [name, mesh] : meshes.all_meshes()) { + out[name] = wmtk::applications::utils::element_count_report_named(mesh); + } + return out; } +} // namespace wmtk::applications::utils diff --git a/applications/utils/src/wmtk/applications/utils/element_count_report.hpp b/applications/utils/src/wmtk/applications/utils/element_count_report.hpp index cb84652355..4d5c776f19 100644 --- a/applications/utils/src/wmtk/applications/utils/element_count_report.hpp +++ b/applications/utils/src/wmtk/applications/utils/element_count_report.hpp @@ -4,10 +4,15 @@ namespace wmtk { class Mesh; + namespace components::multimesh { + class MeshCollection; + } } namespace wmtk::applications::utils { std::vector element_count_report(const Mesh& m); nlohmann::json element_count_report_named(const Mesh& m); + + nlohmann::json element_count_report_named(const components::multimesh::MeshCollection& m); } diff --git a/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.cpp b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.cpp new file mode 100644 index 0000000000..5e40edd77e --- /dev/null +++ b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +namespace wmtk::applications::utils { + std::filesystem::path get_integration_test_data_root(const std::filesystem::path& js_path, const std::filesystem::path& app_path) { + std::ifstream ifs(js_path); + nlohmann::json js; + ifs >> js; + return js[app_path.filename()]["data_folder"]; + + + } +} diff --git a/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp new file mode 100644 index 0000000000..5165576e1d --- /dev/null +++ b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace wmtk::applications::utils { + std::filesystem::path get_integration_test_data_root(const std::filesystem::path& js_path, const std::filesystem::path& app_path) ; +} From 3cbd0c2253b987ea90871fafbe5ed5580bfc3841 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 13:17:48 -0500 Subject: [PATCH 02/11] adding updates to multimesh output and input components --- .../wmtk/components/input/InputOptions.cpp | 22 +- .../wmtk/components/input/InputOptions.hpp | 8 +- .../input/src/wmtk/components/input/input.cpp | 16 +- components/input/tests/input.cpp | 9 +- .../wmtk/components/multimesh/CMakeLists.txt | 3 + .../components/multimesh/MeshCollection.cpp | 16 ++ .../components/multimesh/MeshCollection.hpp | 5 + .../components/multimesh/NamedMultiMesh.cpp | 202 ++++++++++++++++-- .../components/multimesh/NamedMultiMesh.hpp | 18 +- .../multimesh/utils/AttributeDescription.cpp | 20 ++ .../multimesh/utils/AttributeDescription.hpp | 17 ++ .../multimesh/utils/get_attribute.cpp | 8 + .../utils/get_attribute_description.cpp | 21 ++ .../utils/get_attribute_description.hpp | 25 +++ .../components/multimesh/vertex_fusion.cpp | 117 ++++++++++ .../components/multimesh/vertex_fusion.hpp | 18 ++ .../multimesh/tests/named_multimesh.cpp | 33 ++- components/multimesh/tests/vertex_fusion.cpp | 14 ++ .../wmtk/components/output/CMakeLists.txt | 1 + .../wmtk/components/output/OutputOptions.cpp | 15 +- .../wmtk/components/output/OutputOptions.hpp | 2 +- .../output/wmtk/components/output/output.cpp | 56 ++--- .../output/wmtk/components/output/output.hpp | 8 +- .../wmtk/components/output/utils/format.hpp | 29 +++ 24 files changed, 609 insertions(+), 74 deletions(-) create mode 100644 components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.cpp create mode 100644 components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.hpp create mode 100644 components/multimesh/src/wmtk/components/multimesh/vertex_fusion.cpp create mode 100644 components/multimesh/src/wmtk/components/multimesh/vertex_fusion.hpp create mode 100644 components/multimesh/tests/vertex_fusion.cpp create mode 100644 components/output/wmtk/components/output/utils/format.hpp diff --git a/components/input/src/wmtk/components/input/InputOptions.cpp b/components/input/src/wmtk/components/input/InputOptions.cpp index 73e986337d..2324aac747 100644 --- a/components/input/src/wmtk/components/input/InputOptions.cpp +++ b/components/input/src/wmtk/components/input/InputOptions.cpp @@ -16,8 +16,8 @@ namespace nlohmann { void adl_serializer::to_json(json& j, const Type& v) { // - j["file"] = v.file; - j["file"] = v.file.string(); + j["path"] = v.path.string(); + j["validate"] = v.validate; if (!v.name_spec.is_null()) { assert(!v.name_spec_file.has_value()); j["name_spec"] = v.name_spec; @@ -25,7 +25,6 @@ void adl_serializer::to_json(json& j, con j["name_spec_file"] = v.name_spec_file.value(); } - if (v.old_mode) { j["old_mode"] = true; j["ignore_z"] = v.ignore_z_if_zero; // keep around for deprecation purposes @@ -45,10 +44,18 @@ void adl_serializer::to_json(json& j, con void adl_serializer::from_json(const json& j, Type& v) { if (j.is_string()) { - v.file = j.get(); + v.path= j.get(); return; + } else if(j.contains("path")) { + v.path = j["path"].get(); + } else if(j.contains("file")) { + wmtk::logger().warn("InputOptions using file is deprecated, use file"); + v.path = j["file"].get(); + } + if (j.contains("validate")) { + v.validate = j["validate"]; } - v.file = j["file"].get(); + if (j.contains("name_spec")) { v.name_spec = j["name_spec"]; } @@ -85,9 +92,8 @@ void adl_serializer::from_json(const json j["tetrahedron_attributes"].get>()}; } } else { - if (v.imported_attributes.has_value()) { - v.imported_attributes = - j["imported_attributes"].get>>(); + if (j.contains("imported_attributes")) { + v.imported_attributes = j["imported_attributes"].get>>(); } } } diff --git a/components/input/src/wmtk/components/input/InputOptions.hpp b/components/input/src/wmtk/components/input/InputOptions.hpp index a9ebbf0f61..d67a01733a 100644 --- a/components/input/src/wmtk/components/input/InputOptions.hpp +++ b/components/input/src/wmtk/components/input/InputOptions.hpp @@ -18,14 +18,20 @@ class InputOptions public: InputOptions(); ~InputOptions(); - std::filesystem::path file; + std::filesystem::path path; std::optional>> imported_attributes; + // either you can have a name spec in json or you can have it in a file nlohmann::json name_spec; std::optional name_spec_file; + bool validate = false; + + + + // many applications use ignore_z_if_zero and imported attribute sonly works for tets. This flag enables that bool old_mode = false; bool ignore_z_if_zero = false; diff --git a/components/input/src/wmtk/components/input/input.cpp b/components/input/src/wmtk/components/input/input.cpp index 9822a30351..9a2f1b726a 100644 --- a/components/input/src/wmtk/components/input/input.cpp +++ b/components/input/src/wmtk/components/input/input.cpp @@ -6,6 +6,7 @@ #include #include #include "InputOptions.hpp" +#include namespace wmtk::components::input { @@ -16,7 +17,7 @@ std::shared_ptr input( { InputOptions options; options.old_mode = true; - options.file = file; + options.path = file; options.ignore_z_if_zero = ignore_z_if_zero; if (!tetrahedron_attributes.empty()) { options.imported_attributes = {{}, {}, {}, tetrahedron_attributes}; @@ -33,7 +34,7 @@ multimesh::NamedMultiMesh input( const InputOptions& options, const components::utils::PathResolver& resolver) { - const auto [file_path, found] = resolver.resolve(options.file); + const auto [file_path, found] = resolver.resolve(options.path); if (!found) { const auto& paths = resolver.get_paths(); std::vector path_strs; @@ -46,10 +47,11 @@ multimesh::NamedMultiMesh input( log_and_throw_error( "file [{}] not found (input path was [{}], paths searched were [{}]", file_path.string(), - options.file.string(), + options.path.string(), fmt::join(path_strs, ",")); } + std::shared_ptr mesh; if (options.old_mode) { @@ -81,6 +83,14 @@ multimesh::NamedMultiMesh input( ifs >> js; mm.set_names(js); } + if(options.validate) { + for(const auto& mptr: mm.root().get_all_meshes()) { + if(!wmtk::utils::verify_simplex_index_valences(*mptr)) { + throw std::runtime_error(fmt::format("Mesh {} was not valid, check env WMTK_LOGGER_LEVEL=debug for more info", mm.get_name(*mptr))); + } + } + + } return mm; } diff --git a/components/input/tests/input.cpp b/components/input/tests/input.cpp index d0b8a810e9..84087d0ef9 100644 --- a/components/input/tests/input.cpp +++ b/components/input/tests/input.cpp @@ -23,12 +23,13 @@ TEST_CASE("component_input", "[components][input]") auto a = wmtk::components::input::input(input_file, false, {}); json component_json = { - {"file", input_file.string()}, + {"path", input_file.string()}, {"old_mode", true}, {"ignore_z", false}, + {"validate", false}, {"tetrahedron_attributes", json::array()}}; auto opts = component_json.get(); - CHECK(opts.file == input_file); + CHECK(opts.path == input_file); CHECK(opts.ignore_z_if_zero == false); CHECK(opts.old_mode == true); CHECK(opts.old_mode == true); @@ -50,7 +51,7 @@ TEST_CASE("component_input", "[components][input]") nlohmann::json js = "path"; REQUIRE(js.is_string()); auto opts = js.get(); - CHECK(opts.file.string() == "path"); + CHECK(opts.path.string() == "path"); } SECTION("should throw") @@ -58,7 +59,7 @@ TEST_CASE("component_input", "[components][input]") // json component_json = { // {"type", "input"}, // {"name", "input_mesh"}, - // {"file", "In case you ever name your file like that: What is wrong with + // {"path", "In case you ever name your file like that: What is wrong with // you?"}, // {"ignore_z", false}, // {"tetrahedron_attributes", json::array()}}; diff --git a/components/multimesh/src/wmtk/components/multimesh/CMakeLists.txt b/components/multimesh/src/wmtk/components/multimesh/CMakeLists.txt index 95fe98209e..bcf407085b 100644 --- a/components/multimesh/src/wmtk/components/multimesh/CMakeLists.txt +++ b/components/multimesh/src/wmtk/components/multimesh/CMakeLists.txt @@ -10,6 +10,9 @@ set(SRC_FILES from_facet_bijection.hpp from_facet_bijection.cpp + vertex_fusion.hpp + vertex_fusion.cpp + from_boundary.hpp from_boundary.cpp diff --git a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp index 509e64f8c5..47268c3a91 100644 --- a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp @@ -124,4 +124,20 @@ void MeshCollection::make_canonical() } } } +bool MeshCollection::is_valid(bool pass_exceptions) const +{ + for (const auto& [name, nmmptr] : m_meshes) { + if (!nmmptr->is_valid(pass_exceptions)) { + return false; + } + } + return true; +} + + //std::vector get_named_multimeshes(const Mesh& m) const { + + // for(const& [_, nmm]: m_meshes) { + // if(m.is_string + // } + //} } // namespace wmtk::components::multimesh diff --git a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.hpp b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.hpp index 9c7271034d..6fc13d4b72 100644 --- a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.hpp +++ b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.hpp @@ -25,12 +25,17 @@ class MeshCollection NamedMultiMesh& get_named_multimesh(const std::string_view& path); Mesh& get_mesh(const std::string_view& path); + + //std::vector get_named_multimeshes(const Mesh&) const; + // over time meshes can merge and have aliases // Thsi operation removes meshes whose naming structure aren't the root of a naming tree void make_canonical(); std::map all_meshes() const; + // checks whether the meshes / nodes are synchronized. Passes thrown errors if desired + bool is_valid(bool pass_exceptions = false) const; private: std::map> m_meshes; }; diff --git a/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.cpp b/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.cpp index 569de1c982..1d81babc3d 100644 --- a/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.cpp @@ -1,10 +1,10 @@ #include "NamedMultiMesh.hpp" #include -#include #include #include #include #include +#include #include "internal/split_path.hpp" namespace wmtk::components::multimesh { @@ -77,18 +77,44 @@ struct NamedMultiMesh::Node { m_child_indexer.clear(); for (size_t j = 0; j < m_children.size(); ++j) { - m_child_indexer.emplace(m_children[j]->name, j); + const auto& n = m_children[j]->name; + if (m_child_indexer.contains(n)) { + throw std::runtime_error(fmt::format("Child indexer saw the name {} twice", name)); + } + m_child_indexer.emplace(n, j); } } + + + std::vector get_all_paths(const std::string_view& prefix = "") const + { + std::vector paths; + + std::string path; + if (prefix.empty()) { + path = this->name; + } else { + path = fmt::format("{}.{}", prefix, this->name); + } + paths.emplace_back(path); + for (const auto& c : m_children) { + auto n2 = c->get_all_paths(path); + std::copy(n2.begin(), n2.end(), std::back_inserter(paths)); + } + + return paths; + } + friend void to_json( nlohmann::json& nlohmann_json_j, const NamedMultiMesh::Node& nlohmann_json_t) { - nlohmann::json arr = nlohmann::json::array(); + nlohmann::json value; for (const auto& c_ptr : nlohmann_json_t.m_children) { - arr.emplace_back(*c_ptr); + value.update(*c_ptr); } - nlohmann_json_j[nlohmann_json_t.name] = arr; + value["ptr"] = fmt::format("{}", fmt::ptr(&nlohmann_json_t)); + nlohmann_json_j[nlohmann_json_t.name] = value; } }; @@ -111,6 +137,7 @@ NamedMultiMesh::NamedMultiMesh(Mesh& m, const nlohmann::json& root_name) void NamedMultiMesh::set_root(Mesh& m) { m_root = m.shared_from_this(); + populate_default_names(); } void NamedMultiMesh::set_mesh(Mesh& m) @@ -133,7 +160,12 @@ bool NamedMultiMesh::has_mesh(const std::string_view& path) const auto split = internal::split_path(path); #endif Node const* cur_mesh = m_name_root.get(); - assert(*split.begin() == cur_mesh->name || *split.begin() == ""); + const std::string& cur_name = cur_mesh->name; + const bool same_name = *split.begin() == cur_mesh->name; + const bool empty_name = *split.begin() == ""; + if (!(same_name || empty_name)) { + return false; + } for (const auto& token : std::ranges::views::drop(split, 1)) { auto it = cur_mesh->m_child_indexer.find(std::string(token)); if (it == cur_mesh->m_child_indexer.end()) { @@ -155,16 +187,45 @@ auto NamedMultiMesh::get_id(const std::string_view& path) const -> std::vector indices; Node const* cur_mesh = m_name_root.get(); - assert(*split.begin() == cur_mesh->name || *split.begin() == ""); + const bool same_name = *split.begin() == cur_mesh->name; + const bool empty_name = *split.begin() == ""; + if (!(same_name || empty_name)) { + throw std::out_of_range(fmt::format( + "Root name [{}] of path [{}] was not either empty or [{}]", + *split.begin(), + path, + cur_mesh->name)); + } for (const auto& token : std::ranges::views::drop(split, 1)) { - // try { - int64_t index = cur_mesh->m_child_indexer.at(std::string(token)); - //} catch(const std::runtime_error& e) { - // wmtk::logger().warn("Failed to find mesh named {} in mesh list. Path was ", nmm_name, - // path); throw e; - //} - indices.emplace_back(index); - cur_mesh = cur_mesh->m_children[index].get(); + try { + if (cur_mesh->m_child_indexer.size() == 0) { + if (!cur_mesh->m_children.empty()) { + throw std::runtime_error(fmt::format( + "Child indexer wasn't initialized after children were populated (child " + "indexer size {} and child size {} different)", + cur_mesh->m_child_indexer.size(), + cur_mesh->m_children.size())); + } else { + throw std::out_of_range(fmt::format( + "Could not parse {} from name {} because child indexer was empty\nFull " + "Tree: {}\nCurrent subtree: {}", + token, + path, + nlohmann::json(*m_name_root).dump(2), + nlohmann::json(*cur_mesh).dump(2))); + } + } + + int64_t index = cur_mesh->m_child_indexer.at(std::string(token)); + indices.emplace_back(index); + cur_mesh = cur_mesh->m_children[index].get(); + } catch (const std::out_of_range& e) { + wmtk::logger().warn( + "Failed to find mesh named {} in mesh list. Path was {}", + token, + path); + throw e; + } } return indices; @@ -201,6 +262,40 @@ void NamedMultiMesh::set_names(const nlohmann::json& js) } } +void NamedMultiMesh::populate_default_names() +{ + if (!bool(m_name_root)) { + m_name_root = std::make_unique(); + } + std::function sdn; + sdn = [&sdn](Node& n, const Mesh& m) { + const auto& children = m.get_child_meshes(); + if (n.m_children.size() < children.size()) { + n.m_children.resize(children.size()); + } + + for (size_t j = 0; j < children.size(); ++j) { + auto& nnptr = n.m_children[j]; + if (!bool(nnptr)) { + nnptr = std::make_unique(); + } + auto& nn = *nnptr; + if (nn.name.empty()) { + nn.name = fmt::format("{}", j); + } + } + for (size_t j = 0; j < children.size(); ++j) { + sdn(*n.m_children[j], *children[j]); + } + n.update_child_names(); + }; + assert(bool(m_name_root)); + if (m_name_root->name.empty()) { + m_name_root->name = "0"; + } + sdn(*m_name_root, *m_root); +} + std::string_view NamedMultiMesh::root_name() const { assert(bool(m_name_root)); @@ -208,17 +303,38 @@ std::string_view NamedMultiMesh::root_name() const return m_name_root->name; } std::string NamedMultiMesh::name(const std::vector& id) const +{ + return get_name(id); +} +std::string NamedMultiMesh::get_name(const std::vector& id) const { std::vector names; Node const* cur_mesh = m_name_root.get(); names.emplace_back(root_name()); for (const auto& index : id) { - cur_mesh = cur_mesh->m_children[index].get(); + cur_mesh = cur_mesh->m_children.at(index).get(); names.emplace_back(cur_mesh->name); } return fmt::format("{}", fmt::join(names, ".")); } +auto NamedMultiMesh::get_node(const std::vector& id) const -> const Node& +{ + Node const* cur_mesh = m_name_root.get(); + for (const auto& index : id) { + cur_mesh = cur_mesh->m_children[index].get(); + } + return *cur_mesh; +} +auto NamedMultiMesh::get_node(const std::vector& id) -> Node& +{ + Node* cur_mesh = m_name_root.get(); + for (const auto& index : id) { + cur_mesh = cur_mesh->m_children[index].get(); + } + return *cur_mesh; +} + NamedMultiMesh::NamedMultiMesh() : m_name_root(std::make_unique()) {} @@ -237,11 +353,12 @@ NamedMultiMesh::NamedMultiMesh(const NamedMultiMesh& o) {} -std::unique_ptr NamedMultiMesh::get_names_json() const +std::unique_ptr NamedMultiMesh::get_names_json(const std::string_view& path) const { auto js_ptr = std::make_unique(); auto& js = *js_ptr; - js = *m_name_root; + const auto id = get_id(path); + js = get_node(id); return js_ptr; @@ -264,6 +381,10 @@ std::string NamedMultiMesh::get_name(const Mesh& m) const m_name_root->get_name_tokens(id, toks); return fmt::format("{}", fmt::join(toks, ".")); } + + std::string NamedMultiMesh::get_path(const wmtk::attribute::MeshAttributeHandle& m) const { + return fmt::format("{}/{}", get_name(m.mesh()), m.name()); + } void NamedMultiMesh::append_child_mesh_names(const Mesh& parent, const NamedMultiMesh& o) { const std::vector parent_id = get_id(parent); @@ -275,11 +396,6 @@ void NamedMultiMesh::append_child_mesh_names(const Mesh& parent, const NamedMult const auto child_relid = wmtk::multimesh::MultiMeshManager::relative_id( parent.absolute_multi_mesh_id(), o.root().absolute_multi_mesh_id()); - spdlog::error( - "{} {} {}", - fmt::join(parent.absolute_multi_mesh_id(), ","), - fmt::join(o.root().absolute_multi_mesh_id(), ","), - fmt::join(child_relid, ",")); assert(child_relid.size() == 1); const int64_t& id = child_relid[0]; @@ -290,4 +406,44 @@ void NamedMultiMesh::append_child_mesh_names(const Mesh& parent, const NamedMult } cur_mesh->update_child_names(); } +bool NamedMultiMesh::is_valid(bool pass_exceptions) const +{ + // check that every mesh has a valid name + auto throw_or_except = [pass_exceptions](const auto& e) { + if (pass_exceptions) { + throw e; + } + return false; + }; + for (const auto& mptr : m_root->get_all_meshes()) { + try { + wmtk::logger().trace( + "checking if NamedMultiMesh is valid for mesh with relative path [{}] and local " + "path [{}]", + fmt::join(get_id(*mptr), ","), + fmt::join(mptr->absolute_multi_mesh_id(), ",")); + get_name(*mptr); + } catch (const std::range_error& e) { + return throw_or_except(e); + } catch (const std::runtime_error& e) { + return throw_or_except(e); + } + } + + const auto all_paths = m_name_root->get_all_paths(); + if (all_paths.empty()) { + nlohmann::json js = *m_name_root; + throw std::runtime_error("No paths exist in mesh"); + } + for (const std::string& path : all_paths) { + try { + wmtk::logger().trace("checking if NamedMultiMesh is valid for path {}", path); + get_mesh(path); + } catch (const std::runtime_error& e) { + return throw_or_except(e); + } + } + + return true; +} } // namespace wmtk::components::multimesh diff --git a/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp b/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp index ed25016151..c7626966de 100644 --- a/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp +++ b/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp @@ -4,6 +4,9 @@ namespace wmtk { class Mesh; +namespace attribute { + class MeshAttributeHandle; +} } @@ -13,6 +16,7 @@ namespace wmtk::components::multimesh { // The IDs are loaded through a json format, and hte root node can either have a name or be hte // empty string. Even if hte root it has a name, typing it can be skipped by a preceding '.' before // a path +// By default each mesh is named by its index number class NamedMultiMesh { public: @@ -35,13 +39,15 @@ class NamedMultiMesh /// sets just the name of the root mesh, keeping child names the same void set_name(const std::string_view& root_name = ""); void set_names(const nlohmann::json& js); + void populate_default_names(); void set_root(Mesh& m); void append_child_mesh_names(const Mesh& parent, const NamedMultiMesh& o); - std::unique_ptr get_names_json() const; + std::unique_ptr get_names_json(const std::string_view& path) const; std::string_view root_name() const; std::string name(const std::vector& id) const; + std::string get_name(const std::vector& id) const; /// Navigates to the root of the multimesh void set_mesh(Mesh& m); @@ -59,8 +65,18 @@ class NamedMultiMesh // returns the name of a mesh if it lies in this multimesh std::string get_name(const Mesh& m) const; + std::string get_path(const wmtk::attribute::MeshAttributeHandle& m) const; + + // checks whether the meshes / nodes are synchronized. Passes thrown errors if desired + bool is_valid(bool pass_exceptions = false) const; + + private: struct Node; + const Node& get_node(const std::vector& id) const; + Node& get_node(const std::vector& id); + +private: std::shared_ptr m_root; std::unique_ptr m_name_root; }; diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.cpp b/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.cpp index e254258035..a67870b936 100644 --- a/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.cpp @@ -1,5 +1,8 @@ #include "AttributeDescription.hpp" #include +#include +#include +#include #include namespace wmtk::attribute { @@ -66,5 +69,22 @@ std::optional AttributeDescription::primitive_type() const return {}; } } +AttributeDescription::AttributeDescription(const std::string_view& p, const wmtk::attribute::MeshAttributeHandle&mah): path(p), dimension(get_primitive_type_id(mah.primitive_type())), type(mah.held_type()) {} +AttributeDescription::AttributeDescription(const wmtk::attribute::MeshAttributeHandle&mah):AttributeDescription(mah.name(), mah) { +} +//AttributeDescription::AttributeDescription(const MeshCollection& mc, const wmtk::attribute::MeshAttributeHandle&mah): AttributeDescription(mc.get_path(mah), mah) {} +AttributeDescription::AttributeDescription(const NamedMultiMesh& mc, const wmtk::attribute::MeshAttributeHandle&mah): AttributeDescription(mc.get_path(mah), mah) {} + +AttributeDescription::operator std::string() const { + if(dimension.has_value() && type.has_value()) { + return fmt::format("AttributeDescription({},t={},s={})", path, attribute::attribute_type_name(type.value()),dimension.value()); + } else if(dimension.has_value()) { + return fmt::format("AttributeDescription({},s={})", path,dimension.value()); + } else if(type.has_value()) { + return fmt::format("AttributeDescription({},t={})",path, attribute::attribute_type_name(type.value())); + } else { + return fmt::format("AttributeDescription({})", path); + } +} } // namespace wmtk::components::multimesh::utils diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.hpp b/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.hpp index a3ff54fb10..b7a3d597ec 100644 --- a/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.hpp +++ b/components/multimesh/src/wmtk/components/multimesh/utils/AttributeDescription.hpp @@ -6,9 +6,15 @@ #include namespace wmtk::attribute { + class MeshAttributeHandle; WMTK_NLOHMANN_JSON_DECLARATION(AttributeType) } +namespace wmtk::components::multimesh { + class MeshColleciton; + class NamedMultiMesh; +} + namespace wmtk::components::multimesh::utils { @@ -27,6 +33,13 @@ struct AttributeDescription AttributeDescription& operator=(AttributeDescription&&) = default; ~AttributeDescription() = default; + // path will just be the attribute name + AttributeDescription(const wmtk::attribute::MeshAttributeHandle&); + // path will be the longest multimesh name possible + //AttributeDescription(const MeshCollection& mc, const wmtk::attribute::MeshAttributeHandle&); + // a canonical per-path multimesh + AttributeDescription(const NamedMultiMesh& mc, const wmtk::attribute::MeshAttributeHandle&); + AttributeDescription( const std::string_view& p, const std::optional& dim, @@ -52,8 +65,12 @@ struct AttributeDescription auto operator<=>(const AttributeDescription&) const -> std::strong_ordering; auto operator==(const AttributeDescription&) const -> bool; + operator std::string() const; WMTK_NLOHMANN_JSON_FRIEND_DECLARATION(AttributeDescription) + private: + // helper constructor so we can override the path while still reading off other properties from the MAH + AttributeDescription(const std::string_view& p, const wmtk::attribute::MeshAttributeHandle&); }; } // namespace wmtk::components::multimesh::utils diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp index 90d14a3d92..b18d2748e7 100644 --- a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp @@ -116,32 +116,40 @@ wmtk::attribute::MeshAttributeHandle get_attribute( auto add_option = [&](PrimitiveType prim, AT t) { ret = get_attribute(mesh, name, prim, t); + assert(ret.is_valid()); uint8_t dimension = wmtk::get_primitive_type_id(prim); possibilities.emplace_back(AttributeDescription{name, dimension, t}); }; if (pt.has_value() && type.has_value()) { + wmtk::logger().debug("Reading attribute {} with pt {} and type {}", name, primitive_type_name(pt.value()), attribute_type_name(type.value())); add_option(pt.value(), type.value()); } else if (pt.has_value()) { + wmtk::logger().debug("Reading attribute {} with pt {}", name, primitive_type_name(pt.value())); for (AT at : types) { try { + wmtk::logger().trace("Attempting to read attribute {} with pt {} and guess {}", name, primitive_type_name(pt.value()), attribute_type_name(at)); add_option(pt.value(), at); } catch (const attribute_missing_error& e) { continue; } } } else if (type.has_value()) { + wmtk::logger().debug("Reading attribute {} with and type {}", name,attribute_type_name(type.value())); for (PrimitiveType p : wmtk::utils::primitive_below(mesh.top_simplex_type())) { try { + wmtk::logger().trace("Attempting to read attribute {} with guess pt {} and type {}", name, primitive_type_name(p), attribute_type_name(type.value())); add_option(p, type.value()); } catch (const attribute_missing_error& e) { continue; } } } else { + wmtk::logger().debug("Reading attribute {}", name); for (AT at : types) { for (PrimitiveType p : wmtk::utils::primitive_below(mesh.top_simplex_type())) { try { + wmtk::logger().trace("Attempting to read attribute {} with guess pt {} and guess type {}", name, primitive_type_name(p), attribute_type_name(at)); add_option(p, at); } catch (const attribute_missing_error& e) { continue; diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.cpp b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.cpp new file mode 100644 index 0000000000..08a3239d28 --- /dev/null +++ b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.cpp @@ -0,0 +1,21 @@ + + +#include +#include +#include "AttributeDescription.hpp" +namespace wmtk::components::multimesh { +namespace utils { +AttributeDescription get_attribute_handle( + const MeshCollection& collection, + const wmtk::attribute::MeshAttributeHandle& handle) +{ + return {}; +} +AttributeDescription get_attribute_handle( + const NamedMultiMesh& nmm, + const wmtk::attribute::MeshAttributeHandle& handle) +{ + nmm.get_name(handle.mesh()); +} +} // namespace utils +} // namespace wmtk::components::multimesh diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.hpp b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.hpp new file mode 100644 index 0000000000..3ce9a67bee --- /dev/null +++ b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute_description.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "AttributeDescription.hpp" +#include "wmtk/components/multimesh/MeshCollection.hpp" + +namespace wmtk { +class Mesh; +namespace attribute { +class MeshAttributeHandle; +} +} // namespace wmtk + +namespace wmtk::components::multimesh { +class MeshCollection; +class NamedMultiMesh; +namespace utils { +struct AttributeDescription; + +AttributeDescription get_attribute_handle( + const MeshCollection& collection, + const wmtk::attribute::MeshAttributeHandle& handle); +AttributeDescription get_attribute_handle( + const NamedMultiMesh& collection, + const wmtk::attribute::MeshAttributeHandle& handle); +} // namespace utils +} // namespace wmtk::components::multimesh diff --git a/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.cpp b/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.cpp new file mode 100644 index 0000000000..0be4e01f86 --- /dev/null +++ b/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.cpp @@ -0,0 +1,117 @@ +#include "vertex_fusion.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "from_facet_bijection.hpp" +#include + + +namespace wmtk::components::multimesh { + +std::shared_ptr vertex_fusion(const attribute::MeshAttributeHandle& attr, const std::string_view& name, double eps) +{ + Mesh& mesh = const_cast(attr.mesh()); + // get mesh dimension and checks + int64_t mesh_dim = mesh.top_cell_dimension(); + + + // TODO: a lot of matrix copies to remove + + Eigen::MatrixX V; + + wmtk::utils::EigenMatrixWriter writer; + mesh.serialize(writer); + + writer.get_position_matrix(V); + assert(V.rows() == mesh.get_all(PrimitiveType::Vertex).size()); + + // rescale to [0, 1] + Eigen::MatrixXd V_rescale(V.rows(), V.cols()); + + Eigen::RowVectorXd min_pos = V.colwise().minCoeff(); + + + Eigen::RowVectorXd max_pos = V_rescale.colwise().maxCoeff(); + + Eigen::RowVectorXd range = max_pos - min_pos; + V_rescale = V_rescale.array().rowwise() / range.array(); + + MatrixXl S = writer.get_simplex_vertex_matrix(); + assert(S.rows() == mesh.get_all(mesh.top_simplex_type()).size()); + + std::map vertex_map; + std::vector vertex_roots; + Eigen::MatrixXd V_new; + { + utils::DisjointSet ptt(V.rows()); + + const double eps2 = eps * eps; + // TOdo: use a KDTree / accelleration structure ofc + for (int j = 0; j < V.rows(); ++j) { + for (int k = 0; k < V.rows(); ++k) { + if (eps == 0) { + if (V.row(j) == V.row(k)) { + ptt.merge(j, k); + } + } else if ((V.row(j) - V.row(k)).squaredNorm() < eps2) { + ptt.merge(j, k); + } + } + } + + std::map root_indexer; + vertex_roots = ptt.roots(); + for (size_t j = 0; j < vertex_roots.size(); ++j) { + root_indexer[vertex_roots[j]] = j; + } + for (size_t j = 0; j < V.rows(); ++j) { + vertex_map[j] = root_indexer[ptt.get_root(j)]; + } + V_new = V(vertex_roots, Eigen::all); + } + + + wmtk::MatrixXl S_new = S.unaryExpr([&](int64_t index) { return vertex_map[index]; }); + + + std::shared_ptr root_mesh; + switch (mesh_dim) { + case 1: { + auto fusion_mesh = std::make_shared(); + fusion_mesh->initialize(S_new); + root_mesh = fusion_mesh; + break; + } + case 2: { + auto fusion_mesh = std::make_shared(); + fusion_mesh->initialize(S_new); + root_mesh = fusion_mesh; + break; + } + case 3: { + auto fusion_mesh = std::make_shared(); + fusion_mesh->initialize(S_new); + root_mesh = fusion_mesh; + break; + } + default: { + throw std::runtime_error("mesh dimension not supported"); + } + } + Mesh& child = mesh; + Mesh& parent = *root_mesh; + + from_facet_bijection(parent, child); + mesh_utils::set_matrix_attribute(V_new, std::string(name), PrimitiveType::Vertex, *root_mesh); + return root_mesh; +} +} // namespace wmtk::components::multimesh diff --git a/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.hpp b/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.hpp new file mode 100644 index 0000000000..644e791904 --- /dev/null +++ b/components/multimesh/src/wmtk/components/multimesh/vertex_fusion.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace wmtk { +class Mesh; +namespace attribute { + class MeshAttributeHandle; + +} +} // namespace wmtk +namespace wmtk::components::multimesh { + +// returns the new root mesh (the input mesh becomes a child mesh) +std::shared_ptr vertex_fusion(const attribute::MeshAttributeHandle& m, const std::string_view& name, double eps = 0.0); + +} // namespace wmtk::components::multimesh diff --git a/components/multimesh/tests/named_multimesh.cpp b/components/multimesh/tests/named_multimesh.cpp index cc8e34a761..e56c8988d9 100644 --- a/components/multimesh/tests/named_multimesh.cpp +++ b/components/multimesh/tests/named_multimesh.cpp @@ -13,17 +13,20 @@ using json = nlohmann::json; - - TEST_CASE("named_multimesh_parse", "[components][multimesh]") { { auto m = make_mesh(); wmtk::components::multimesh::NamedMultiMesh named_mm; named_mm.set_mesh(*m); + CHECK(std::vector{} == named_mm.get_id("")); + CHECK(std::vector{} == named_mm.get_id("0")); + CHECK(m == named_mm.get_mesh("").shared_from_this()); named_mm.set_name("roo"); + CHECK(std::vector{} == named_mm.get_id("")); + CHECK(m == named_mm.get_mesh("").shared_from_this()); CHECK(std::vector{} == named_mm.get_id("roo")); CHECK(m == named_mm.root().shared_from_this()); CHECK(m == named_mm.get_mesh("roo").shared_from_this()); @@ -37,6 +40,20 @@ TEST_CASE("named_multimesh_parse", "[components][multimesh]") wmtk::components::multimesh::NamedMultiMesh named_mm; named_mm.set_mesh(*m); + + //CHECK(std::vector{} == named_mm.get_id("")); + //CHECK(std::vector{} == named_mm.get_id("0")); + //CHECK(m == named_mm.get_mesh("").shared_from_this()); + + CHECK(std::vector{0} == named_mm.get_id("0.0")); + CHECK(std::vector{0} == named_mm.get_id(".0")); + + CHECK( + m->get_multi_mesh_child_mesh({0}).shared_from_this() == + named_mm.get_mesh("0.0").shared_from_this()); + CHECK( + m->get_multi_mesh_child_mesh({0}).shared_from_this() == + named_mm.get_mesh(".0").shared_from_this()); { nlohmann::json js; js["roo"] = nlohmann::json::array({"child"}); @@ -76,6 +93,18 @@ TEST_CASE("named_multimesh_parse", "[components][multimesh]") make_child(*m, {1, 1}); named_mm.set_mesh(*m); + + + CHECK(std::vector{} == named_mm.get_id("")); + auto check = [&](const std::vector& name) { + CHECK(name == named_mm.get_id(fmt::format(".{}", fmt::join(name, ".")))); + }; + check({0}); + check({1}); + check({0, 0}); + check({1, 0}); + check({1, 1}); + check({0, 0, 0}); } { nlohmann::json js; diff --git a/components/multimesh/tests/vertex_fusion.cpp b/components/multimesh/tests/vertex_fusion.cpp new file mode 100644 index 0000000000..d21177f77b --- /dev/null +++ b/components/multimesh/tests/vertex_fusion.cpp @@ -0,0 +1,14 @@ +#include + + +#include + +#include +#include + +TEST_CASE("trimesh_fuse_edge", "[components][multimesh]") +{ +} +TEST_CASE("tetmesh_fuse_edge", "[components][multimesh]") +{ +} diff --git a/components/output/wmtk/components/output/CMakeLists.txt b/components/output/wmtk/components/output/CMakeLists.txt index 33adb82588..6b1b33f999 100644 --- a/components/output/wmtk/components/output/CMakeLists.txt +++ b/components/output/wmtk/components/output/CMakeLists.txt @@ -9,6 +9,7 @@ set(SRC_FILES output.hpp OutputOptions.hpp OutputOptions.cpp + utils/format.hpp ) diff --git a/components/output/wmtk/components/output/OutputOptions.cpp b/components/output/wmtk/components/output/OutputOptions.cpp index 05507e6501..0c44c7aab7 100644 --- a/components/output/wmtk/components/output/OutputOptions.cpp +++ b/components/output/wmtk/components/output/OutputOptions.cpp @@ -10,7 +10,7 @@ namespace wmtk::components::output { WMTK_NLOHMANN_JSON_FRIEND_TO_JSON_PROTOTYPE(OutputOptions) { - WMTK_NLOHMANN_ASSIGN_TYPE_TO_JSON(file, type) + WMTK_NLOHMANN_ASSIGN_TYPE_TO_JSON(path, type) if (nlohmann_json_t.mesh_name_path.has_value()) { nlohmann_json_j["mesh_name_path"] = nlohmann_json_t.mesh_name_path.value(); } @@ -33,17 +33,20 @@ WMTK_NLOHMANN_JSON_FRIEND_TO_JSON_PROTOTYPE(OutputOptions) WMTK_NLOHMANN_JSON_FRIEND_FROM_JSON_PROTOTYPE(OutputOptions) { if (nlohmann_json_j.is_string()) { - nlohmann_json_t.file = nlohmann_json_j.get(); - } else { - nlohmann_json_t.file = nlohmann_json_j["file"].get(); + nlohmann_json_t.path = nlohmann_json_j.get(); + } else if(nlohmann_json_j.contains("path")) { + nlohmann_json_t.path = nlohmann_json_j["path"].get(); + } else if(nlohmann_json_j.contains("file")) { + wmtk::logger().warn("OutputOptions using file is deprecated, use file"); + nlohmann_json_t.path = nlohmann_json_j["file"].get(); } if (nlohmann_json_j.contains("type")) { nlohmann_json_t.type = nlohmann_json_j["type"]; } else { - nlohmann_json_t.type = nlohmann_json_t.file.extension().string(); + nlohmann_json_t.type = nlohmann_json_t.path.extension().string(); wmtk::logger().debug( "Guessing extension type of [{}] is [{}]", - nlohmann_json_t.file, + nlohmann_json_t.path, nlohmann_json_t.type); } if (nlohmann_json_j.contains("position_attribute")) { diff --git a/components/output/wmtk/components/output/OutputOptions.hpp b/components/output/wmtk/components/output/OutputOptions.hpp index f3b7565af6..ac4acb1a2f 100644 --- a/components/output/wmtk/components/output/OutputOptions.hpp +++ b/components/output/wmtk/components/output/OutputOptions.hpp @@ -8,7 +8,7 @@ namespace wmtk::components::output { struct OutputOptions { - std::filesystem::path file; + std::filesystem::path path; std::string type; diff --git a/components/output/wmtk/components/output/output.cpp b/components/output/wmtk/components/output/output.cpp index 511d84b5a4..343082fc95 100644 --- a/components/output/wmtk/components/output/output.cpp +++ b/components/output/wmtk/components/output/output.cpp @@ -1,14 +1,15 @@ #include "output.hpp" -#include #include #include +#include +#include +#include #include #include #include #include #include "OutputOptions.hpp" -#include namespace wmtk::components::output { @@ -50,49 +51,56 @@ void output_hdf5(const Mesh& mesh, const std::filesystem::path& file) } - -void output( - const Mesh& mesh, - const OutputOptions& opts) +void output(const Mesh& mesh, const OutputOptions& opts) { - if (opts.type == ".vtu") { - assert( - opts.position_attribute.index() != std::variant_npos); - std::string name = std::visit([](const auto& v) -> std::string{ + assert(opts.position_attribute.index() != std::variant_npos); + std::string name = std::visit( + [](const auto& v) -> std::string { using T = std::decay_t; - if constexpr(std::is_same_v) { - return v; - } else if constexpr(std::is_same_v) { - return v.name(); + if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_same_v) { + assert(v.is_valid()); + return v.name(); } - }, opts.position_attribute); + }, + opts.position_attribute); std::array out = {{false, false, false, false}}; for (int64_t d = 0; d <= mesh.top_cell_dimension(); ++d) { out[d] = true; } - ParaviewWriter writer(opts.file,name, mesh, out[0], out[1], out[2], out[3]); + ParaviewWriter writer(opts.path, name, mesh, out[0], out[1], out[2], out[3]); mesh.serialize(writer); } else if (opts.type == ".hdf5") { - output_hdf5(mesh, opts.file); + output_hdf5(mesh, opts.path); } else throw std::runtime_error( - fmt::format("Unable to write file [{}] of extension [{}]", - opts.file, opts.type)); + fmt::format("Unable to write file [{}] of extension [{}]", opts.path, opts.type)); } void output( const multimesh::NamedMultiMesh& mesh, - const OutputOptions& opts) + const OutputOptions& opts, + const std::string_view& mesh_path) { - output(mesh.root(), opts); + output(mesh.get_mesh(mesh_path), opts); - if(opts.mesh_name_path.has_value()) { + if (opts.mesh_name_path.has_value()) { const auto& path = opts.mesh_name_path.value(); std::ofstream ofs(path); - ofs<< *mesh.get_names_json(); + ofs << *mesh.get_names_json(mesh_path); } +} +void output( + const multimesh::MeshCollection& mesh_col, + const std::map& opts) +{ + for (const auto& [path, single_opts] : opts) { + const multimesh::NamedMultiMesh& nmm = mesh_col.get_named_multimesh(path); + output(nmm, single_opts, path); + } } -} // namespace wmtk::components +} // namespace wmtk::components::output diff --git a/components/output/wmtk/components/output/output.hpp b/components/output/wmtk/components/output/output.hpp index c84c8212bc..eedb83e472 100644 --- a/components/output/wmtk/components/output/output.hpp +++ b/components/output/wmtk/components/output/output.hpp @@ -8,6 +8,7 @@ namespace wmtk::components { namespace multimesh { class NamedMultiMesh; + class MeshCollection; } namespace output { @@ -68,7 +69,12 @@ void output( void output( const multimesh::NamedMultiMesh& mesh, - const OutputOptions&); + const OutputOptions&, const std::string_view& mesh_path = ""); + + +void output( + const multimesh::MeshCollection& mesh, + const std::map&); /** * @brief Write the mesh to file. diff --git a/components/output/wmtk/components/output/utils/format.hpp b/components/output/wmtk/components/output/utils/format.hpp new file mode 100644 index 0000000000..4b19bac533 --- /dev/null +++ b/components/output/wmtk/components/output/utils/format.hpp @@ -0,0 +1,29 @@ +#include +#include +namespace wmtk::components::output::utils { + +template +auto format(const OutputOptions& input, Args&&... args) -> OutputOptions +{ + OutputOptions opt = input; + using Var = std::decay_t; + opt.path = fmt::format(fmt::runtime(input.path.string()), std::forward(args)...); + + /* + opt.position_attribute = std::visit( + [&](auto&& attr) -> Var { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return attr; + } else { + throw std::runtime_error("Cannot use meshattributehandle as a position attribute"); + return attribute::MeshAttributeHandle{}; + } + }, + input.position_attribute); + */ + + return opt; +} + +} // namespace wmtk::components::output::utils From 29858d750b7a74f7afa112f677f950cea84fb00f Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 13:33:38 -0500 Subject: [PATCH 03/11] adding comments to application utils --- .../applications/utils/element_count_report.hpp | 13 +++++++++++-- .../utils/get_integration_test_data_root.hpp | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/applications/utils/src/wmtk/applications/utils/element_count_report.hpp b/applications/utils/src/wmtk/applications/utils/element_count_report.hpp index 4d5c776f19..4c92859c49 100644 --- a/applications/utils/src/wmtk/applications/utils/element_count_report.hpp +++ b/applications/utils/src/wmtk/applications/utils/element_count_report.hpp @@ -11,8 +11,17 @@ namespace wmtk { namespace wmtk::applications::utils { - std::vector element_count_report(const Mesh& m); - nlohmann::json element_count_report_named(const Mesh& m); +// Generates size statistics for each mesh in a mesh collection. Useful for "reports" used in integratino tests +// Something like: +// {"pos": {"vertices": 3, "edges": 24}, "pos.uv": {"vertices": 0, "edges": 23}} nlohmann::json element_count_report_named(const components::multimesh::MeshCollection& m); + +// produces a json file corresponding colloquial simplex type names to the counts +// Soemthing like: {"vertices": 3, "edges": 24} + nlohmann::json element_count_report_named(const Mesh& m); + + +// produces the number of simplices of each type in a mesh - useful for generating json reports on the elements in a mesh + std::vector element_count_report(const Mesh& m); } diff --git a/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp index 5165576e1d..18081303db 100644 --- a/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp +++ b/applications/utils/src/wmtk/applications/utils/get_integration_test_data_root.hpp @@ -2,5 +2,6 @@ #include namespace wmtk::applications::utils { + // allows for some applications to retrieve the data folder used for their integration tests using the json manifest that exists in each build folder (typically as "test_config.json"). Requires knowing which app they are (i.e isotropic_remeshing_app) as app_path std::filesystem::path get_integration_test_data_root(const std::filesystem::path& js_path, const std::filesystem::path& app_path) ; } From e073b461c4ce9a6156af3b97da2d17bba18cc87b Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 14:46:34 -0500 Subject: [PATCH 04/11] moving debug to trace in get_attribute --- .../src/wmtk/components/multimesh/utils/get_attribute.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp index b18d2748e7..a3760c2e3d 100644 --- a/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/utils/get_attribute.cpp @@ -121,11 +121,11 @@ wmtk::attribute::MeshAttributeHandle get_attribute( possibilities.emplace_back(AttributeDescription{name, dimension, t}); }; if (pt.has_value() && type.has_value()) { - wmtk::logger().debug("Reading attribute {} with pt {} and type {}", name, primitive_type_name(pt.value()), attribute_type_name(type.value())); + wmtk::logger().trace("Reading attribute {} with pt {} and type {}", name, primitive_type_name(pt.value()), attribute_type_name(type.value())); add_option(pt.value(), type.value()); } else if (pt.has_value()) { - wmtk::logger().debug("Reading attribute {} with pt {}", name, primitive_type_name(pt.value())); + wmtk::logger().trace("Reading attribute {} with pt {}", name, primitive_type_name(pt.value())); for (AT at : types) { try { wmtk::logger().trace("Attempting to read attribute {} with pt {} and guess {}", name, primitive_type_name(pt.value()), attribute_type_name(at)); @@ -135,7 +135,7 @@ wmtk::attribute::MeshAttributeHandle get_attribute( } } } else if (type.has_value()) { - wmtk::logger().debug("Reading attribute {} with and type {}", name,attribute_type_name(type.value())); + wmtk::logger().trace("Reading attribute {} with and type {}", name,attribute_type_name(type.value())); for (PrimitiveType p : wmtk::utils::primitive_below(mesh.top_simplex_type())) { try { wmtk::logger().trace("Attempting to read attribute {} with guess pt {} and type {}", name, primitive_type_name(p), attribute_type_name(type.value())); @@ -145,7 +145,7 @@ wmtk::attribute::MeshAttributeHandle get_attribute( } } } else { - wmtk::logger().debug("Reading attribute {}", name); + wmtk::logger().trace("Reading attribute {}", name); for (AT at : types) { for (PrimitiveType p : wmtk::utils::primitive_below(mesh.top_simplex_type())) { try { From b2930dbf462e6945e5a4d4665f867107f4755d0e Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 14:47:01 -0500 Subject: [PATCH 05/11] moving debug to trace in MeshCollection --- .../src/wmtk/components/multimesh/MeshCollection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp index 47268c3a91..4bbfdfb9c5 100644 --- a/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/MeshCollection.cpp @@ -61,7 +61,7 @@ const NamedMultiMesh& MeshCollection::get_named_multimesh(const std::string_view #endif const auto nmm_name = *split.begin(); if (nmm_name.empty() && m_meshes.size() == 1) { - wmtk::logger().debug("MeshCollection accessed with an empty name, but has only 1 mesh so " + wmtk::logger().trace("MeshCollection accessed with an empty name, but has only 1 mesh so " "assuming that is the right mesh"); return *m_meshes.begin()->second; } @@ -91,7 +91,7 @@ NamedMultiMesh& MeshCollection::get_named_multimesh(const std::string_view& path #endif const auto nmm_name = *split.begin(); if (nmm_name.empty() && m_meshes.size() == 1) { - wmtk::logger().debug("MeshCollection accessed with an empty name, but has only 1 mesh so " + wmtk::logger().trace("MeshCollection accessed with an empty name, but has only 1 mesh so " "assuming that is the right mesh"); return *m_meshes.begin()->second; } From 8a5669d0d937e175b201afa3687d264010a11d45 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Fri, 13 Dec 2024 16:45:57 -0500 Subject: [PATCH 06/11] cleaning up mah and making attributetype printable --- src/wmtk/attribute/AttributeType.cpp | 24 ++++++++++++++++++++++ src/wmtk/attribute/AttributeType.hpp | 17 +++++++++------ src/wmtk/attribute/CMakeLists.txt | 1 + src/wmtk/attribute/MeshAttributeHandle.hpp | 21 +++++++------------ 4 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 src/wmtk/attribute/AttributeType.cpp diff --git a/src/wmtk/attribute/AttributeType.cpp b/src/wmtk/attribute/AttributeType.cpp new file mode 100644 index 0000000000..a3fc3185ff --- /dev/null +++ b/src/wmtk/attribute/AttributeType.cpp @@ -0,0 +1,24 @@ +#include "AttributeType.hpp" +namespace wmtk::attribute { +const std::string_view attribute_type_name(AttributeType pt) { + + switch(pt) { + case AttributeType::Char: + return attribute_type_traits::name; + case AttributeType::Int64: + return attribute_type_traits::name; + case AttributeType::Double: + return attribute_type_traits::name; + case AttributeType::Rational: + return attribute_type_traits::name; + default: + break; + } + return ""; +} + +const std::string_view attribute_type_traits::name = "Rational"; +const std::string_view attribute_type_traits::name = "Double"; +const std::string_view attribute_type_traits::name = "Int64"; +const std::string_view attribute_type_traits::name = "Char"; +} diff --git a/src/wmtk/attribute/AttributeType.hpp b/src/wmtk/attribute/AttributeType.hpp index 3cccfddf19..c7e3938f50 100644 --- a/src/wmtk/attribute/AttributeType.hpp +++ b/src/wmtk/attribute/AttributeType.hpp @@ -5,32 +5,36 @@ namespace wmtk::attribute { enum class AttributeType { Char = 0, Int64 = 1, Double = 2, Rational = 3 }; template -struct type_from_attribute_type_enum +struct attribute_type_traits { }; template <> -struct type_from_attribute_type_enum +struct attribute_type_traits { using type = char; + const static std::string_view name; }; template <> -struct type_from_attribute_type_enum +struct attribute_type_traits { using type = double; + const static std::string_view name; }; template <> -struct type_from_attribute_type_enum +struct attribute_type_traits { using type = int64_t; + const static std::string_view name; }; template <> -struct type_from_attribute_type_enum +struct attribute_type_traits { using type = wmtk::Rational; + const static std::string_view name; }; template -using type_from_attribute_type_enum_t = typename type_from_attribute_type_enum::type; +using type_from_attribute_type_enum_t = typename attribute_type_traits::type; template inline constexpr auto attribute_type_enum_from_type() -> AttributeType @@ -53,4 +57,5 @@ inline constexpr auto attribute_type_enum_from_type() -> AttributeType return AttributeType::Char; } } +const std::string_view attribute_type_name(AttributeType pt); } // namespace wmtk::attribute diff --git a/src/wmtk/attribute/CMakeLists.txt b/src/wmtk/attribute/CMakeLists.txt index cd8f3506ca..7b2d12439b 100644 --- a/src/wmtk/attribute/CMakeLists.txt +++ b/src/wmtk/attribute/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRC_FILES Accessor.hpp AttributeType.hpp + AttributeType.cpp ) target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) diff --git a/src/wmtk/attribute/MeshAttributeHandle.hpp b/src/wmtk/attribute/MeshAttributeHandle.hpp index 5faf2539e4..7c895088f0 100644 --- a/src/wmtk/attribute/MeshAttributeHandle.hpp +++ b/src/wmtk/attribute/MeshAttributeHandle.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace wmtk { class Mesh; @@ -76,21 +77,13 @@ class MeshAttributeHandle bool operator==(const MeshAttributeHandle& o) const { -#if defined(MTAO_DEBUG_MESH_COMP) - std::visit( - [&](const auto& h, const auto& oh) { - spdlog::warn( - "{} {} == {} {}", - std::string(h), - fmt::ptr(m_mesh), - std::string(oh), - fmt::ptr(m_mesh)); - }, - m_handle, - o.m_handle); -#endif - return m_handle == o.m_handle && m_mesh == o.m_mesh; + return std::tie(m_mesh, m_handle) == std::tie(o.m_mesh, o.m_handle); } + bool operator<(const MeshAttributeHandle& o) const + { + return std::tie(m_mesh, m_handle) < std::tie(o.m_mesh, o.m_handle); + } + // reutrns if the target mesh is the same as the one represented in the handle bool is_same_mesh(const Mesh&) const; From 09d6105c5ca7800570120b4da5ab12216fd48c31 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Sat, 14 Dec 2024 20:11:24 -0500 Subject: [PATCH 07/11] adding test for attribute type name functions --- components/CMakeLists.txt | 4 +++- components/cmake/add_component_test.cmake | 2 +- components/multimesh/CMakeLists.txt | 2 ++ tests/attributes/CMakeLists.txt | 2 ++ tests/attributes/attribute_type.cpp | 18 ++++++++++++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/attributes/attribute_type.cpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c749617482..88ff42b97c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -8,7 +8,9 @@ include(add_component_test) # wmtk::component_utils add_subdirectory(utils) -if(WILDMESHING_TOOLKIT_TOPLEVEL_PROJECT) +option(WMTK_ENABLE_COMPONENT_TESTS "Enable unit tests for components" ${WILDMESHING_TOOLKIT_TOPLEVEL_PROJECT}) + +if(WMTK_ENABLE_COMPONENT_TESTS) add_subdirectory(tests) endif() diff --git a/components/cmake/add_component_test.cmake b/components/cmake/add_component_test.cmake index 3f37286467..d03336ccf1 100644 --- a/components/cmake/add_component_test.cmake +++ b/components/cmake/add_component_test.cmake @@ -1,6 +1,6 @@ function(add_component_test COMPONENT_TARGET_NAME ...) - if(NOT WILDMESHING_TOOLKIT_TOPLEVEL_PROJECT) + if(NOT WMTK_ENABLE_COMPONENT_TESTS) return() endif() list(REMOVE_AT ARGV 0) diff --git a/components/multimesh/CMakeLists.txt b/components/multimesh/CMakeLists.txt index fd6bd15574..01b1c1c952 100644 --- a/components/multimesh/CMakeLists.txt +++ b/components/multimesh/CMakeLists.txt @@ -1,3 +1,5 @@ set(COMPONENT_NAME multimesh) add_subdirectory("src/wmtk/components/${COMPONENT_NAME}") +if(WMTK_ENABLE_COMPONENT_TESTS) add_subdirectory("tests") +endif() diff --git a/tests/attributes/CMakeLists.txt b/tests/attributes/CMakeLists.txt index 0961cf7e74..cb8927e215 100644 --- a/tests/attributes/CMakeLists.txt +++ b/tests/attributes/CMakeLists.txt @@ -8,6 +8,8 @@ set(TEST_SOURCES old_wmtk_attributecollection.cpp transaction_stack.cpp + + attribute_type.cpp ) target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/attributes/attribute_type.cpp b/tests/attributes/attribute_type.cpp new file mode 100644 index 0000000000..fe640680c1 --- /dev/null +++ b/tests/attributes/attribute_type.cpp @@ -0,0 +1,18 @@ +#include + +#include +#include + + +using namespace wmtk::attribute; + +TEST_CASE("test_attribute_type_names", "[attributes]") +{ + using AT = AttributeType; + CHECK(attribute_type_name(AT::Char) == "Char"); + CHECK(attribute_type_name(AT::Double) == "Double"); + CHECK(attribute_type_name(AT::Int64) == "Int64"); + CHECK(attribute_type_name(AT::Rational) == "Rational"); + +} + From f58b053d188803bcec86e58ee57622af54d9af25 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Sat, 14 Dec 2024 20:29:46 -0500 Subject: [PATCH 08/11] converting string_view to string in catch2 --- tests/attributes/attribute_type.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/attributes/attribute_type.cpp b/tests/attributes/attribute_type.cpp index fe640680c1..09f5f3caee 100644 --- a/tests/attributes/attribute_type.cpp +++ b/tests/attributes/attribute_type.cpp @@ -9,10 +9,11 @@ using namespace wmtk::attribute; TEST_CASE("test_attribute_type_names", "[attributes]") { using AT = AttributeType; - CHECK(attribute_type_name(AT::Char) == "Char"); - CHECK(attribute_type_name(AT::Double) == "Double"); - CHECK(attribute_type_name(AT::Int64) == "Int64"); - CHECK(attribute_type_name(AT::Rational) == "Rational"); + // converting to string because some compilers fail with this combo of catch + string_view comparisons? + CHECK(std::string(attribute_type_name(AT::Char)) == "Char"); + CHECK(std::string(attribute_type_name(AT::Double)) == "Double"); + CHECK(std::string(attribute_type_name(AT::Int64)) == "Int64"); + CHECK(std::string(attribute_type_name(AT::Rational)) == "Rational"); } From 01aa6a1f664b7443ded6d06e8e5424dffffde273 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Sat, 14 Dec 2024 20:33:06 -0500 Subject: [PATCH 09/11] making input component only do tests if component test flag is active --- components/input/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/input/CMakeLists.txt b/components/input/CMakeLists.txt index 95150517d2..4486ff7448 100644 --- a/components/input/CMakeLists.txt +++ b/components/input/CMakeLists.txt @@ -1,3 +1,5 @@ set(COMPONENT_NAME input) add_subdirectory("src/wmtk/components/input") +if(WMTK_ENABLE_COMPONENT_TESTS) add_subdirectory("tests") +endif() From 165dd55e7b295ef492c277001b8b13fb5428e6c9 Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Mon, 16 Dec 2024 14:06:36 -0500 Subject: [PATCH 10/11] merging more updates from isotropic remeshing --- src/wmtk/utils/CMakeLists.txt | 9 + src/wmtk/utils/DisjointSet.hpp | 77 ++++++ src/wmtk/utils/EigenMatrixWriter.cpp | 100 +++++-- src/wmtk/utils/EigenMatrixWriter.hpp | 11 + .../utils/internal/IndexSimplexMapper.cpp | 253 ++++++++++++++++++ .../utils/internal/IndexSimplexMapper.hpp | 72 +++++ .../utils/verify_simplex_index_valences.cpp | 150 +++++++++++ .../utils/verify_simplex_index_valences.hpp | 8 + tests/utils/CMakeLists.txt | 2 + tests/utils/disjoint_set.cpp | 35 +++ tests/utils/valence_checker.cpp | 108 ++++++++ 11 files changed, 803 insertions(+), 22 deletions(-) create mode 100644 src/wmtk/utils/DisjointSet.hpp create mode 100644 src/wmtk/utils/internal/IndexSimplexMapper.cpp create mode 100644 src/wmtk/utils/internal/IndexSimplexMapper.hpp create mode 100644 src/wmtk/utils/verify_simplex_index_valences.cpp create mode 100644 src/wmtk/utils/verify_simplex_index_valences.hpp create mode 100644 tests/utils/disjoint_set.cpp create mode 100644 tests/utils/valence_checker.cpp diff --git a/src/wmtk/utils/CMakeLists.txt b/src/wmtk/utils/CMakeLists.txt index 046c41ee4b..1a33157bc7 100644 --- a/src/wmtk/utils/CMakeLists.txt +++ b/src/wmtk/utils/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRC_FILES mesh_utils.hpp mesh_utils.cpp TupleInspector.hpp + DisjointSet.hpp filter_pointers_to_derived.hpp @@ -74,6 +75,14 @@ set(SRC_FILES DynamicArray.hpp DynamicArray.hxx + + DisjointSet.hpp + internal/IndexSimplexMapper.hpp + internal/IndexSimplexMapper.cpp + + verify_simplex_index_valences.hpp + verify_simplex_index_valences.cpp + ) target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) diff --git a/src/wmtk/utils/DisjointSet.hpp b/src/wmtk/utils/DisjointSet.hpp new file mode 100644 index 0000000000..ab3ed0ef74 --- /dev/null +++ b/src/wmtk/utils/DisjointSet.hpp @@ -0,0 +1,77 @@ +#pragma once +#include +#include + +namespace wmtk::utils { + +class DisjointSet +{ +public: + DisjointSet(size_t size); + + void merge(size_t a, size_t b); + + size_t get_root(size_t a) const; + bool is_root(size_t a) const; + + std::vector roots() const; + + +private: + void merge_sorted(size_t lower, size_t higher); + // overwrites all roots to simplify the code + size_t get_root_recursive(size_t a); + std::vector _parents; +}; +inline DisjointSet::DisjointSet(size_t size) + : _parents(size) +{ + std::iota(_parents.begin(), _parents.end(), 0); +} + + +inline bool DisjointSet::is_root(size_t a) const +{ + size_t r = _parents[a]; + return r == a; +} +inline size_t DisjointSet::get_root(size_t a) const +{ + size_t r = _parents[a]; + while (r != a) { + a = r; + r = _parents[a]; + } + return r; +} +inline size_t DisjointSet::get_root_recursive(size_t a) +{ + size_t& r = _parents[a]; + if (r == a) { + return a; + } else { + return r = get_root_recursive(r); + } +} +inline void DisjointSet::merge(size_t a, size_t b) +{ + a = get_root_recursive(a); + b = get_root_recursive(b); + if (a > b) { + _parents[a] = b; + } else { + _parents[b] = a; + } +} +inline std::vector DisjointSet::roots() const +{ + std::vector r; + r.reserve(_parents.size()); + for (size_t j = 0; j < _parents.size(); ++j) { + if (is_root(j)) { + r.emplace_back(j); + } + } + return r; +} +} // namespace wmtk::utils diff --git a/src/wmtk/utils/EigenMatrixWriter.cpp b/src/wmtk/utils/EigenMatrixWriter.cpp index 7d67db9302..00181dd7fa 100644 --- a/src/wmtk/utils/EigenMatrixWriter.cpp +++ b/src/wmtk/utils/EigenMatrixWriter.cpp @@ -1,4 +1,6 @@ #include "EigenMatrixWriter.hpp" +#include +#include namespace wmtk::utils { @@ -28,16 +30,66 @@ void EigenMatrixWriter::get_EV_matrix(MatrixX& matrix) get_int64_t_matrix("m_ev", PrimitiveType::Edge, matrix); } +auto EigenMatrixWriter::get_simplex_vertex_matrix() const -> MatrixXl +{ + const static std::array, 3> keys = { + {std::make_pair("m_tv", PrimitiveType::Tetrahedron), + std::make_pair("m_fv", PrimitiveType::Triangle), + std::make_pair("m_ev", PrimitiveType::Edge)}}; + for (const auto& [n, pt] : keys) { + try { + return get_matrix(n, pt); + } catch (const std::out_of_range& e) { + continue; + } + } + assert(false); + return {}; +} + +template +bool EigenMatrixWriter::has_matrix(const std::string& name, const PrimitiveType type) + const +{ + try { + get_matrix(name,type); + } catch (const std::out_of_range& e) { + return false; + } + return true; +} + +template +Eigen::MatrixX EigenMatrixWriter::get_matrix(const std::string& name, const PrimitiveType type) + const +{ + auto pair = std::make_pair(name, type); + try { + if constexpr (std::is_same_v) { + return chars.at(pair); + } else if constexpr (std::is_same_v) { + return doubles.at(pair); + } else if constexpr (std::is_same_v) { + return int64_ts.at(pair); + } else if constexpr (std::is_same_v) { + return Rationals.at(pair); + } + } catch (const std::out_of_range& e) { + throw std::out_of_range(fmt::format( + "No attribute named {} with primitive {} found on {}", + name, + primitive_type_name(type), + typeid(T).name())); + } + return {}; +} + void EigenMatrixWriter::get_double_matrix( const std::string& name, const PrimitiveType type, MatrixX& matrix) { - if (doubles.find(std::make_pair(name, type)) != doubles.end()) { - matrix = doubles[std::make_pair(name, type)]; - } else { - throw std::runtime_error("No attribute named " + name); - } + matrix = get_matrix(name, type); } void EigenMatrixWriter::get_int64_t_matrix( @@ -45,11 +97,7 @@ void EigenMatrixWriter::get_int64_t_matrix( const PrimitiveType type, MatrixX& matrix) { - if (int64_ts.find(std::make_pair(name, type)) != int64_ts.end()) { - matrix = int64_ts[std::make_pair(name, type)]; - } else { - throw std::runtime_error("No attribute named " + name); - } + matrix = get_matrix(name, type); } void EigenMatrixWriter::get_char_matrix( @@ -57,11 +105,7 @@ void EigenMatrixWriter::get_char_matrix( const PrimitiveType type, MatrixX& matrix) { - if (chars.find(std::make_pair(name, type)) != chars.end()) { - matrix = chars[std::make_pair(name, type)]; - } else { - throw std::runtime_error("No attribute named " + name); - } + matrix = get_matrix(name, type); } void EigenMatrixWriter::get_Rational_matrix( @@ -69,11 +113,7 @@ void EigenMatrixWriter::get_Rational_matrix( const PrimitiveType type, MatrixX& matrix) { - if (Rationals.find(std::make_pair(name, type)) != Rationals.end()) { - matrix = Rationals[std::make_pair(name, type)]; - } else { - throw std::runtime_error("No attribute named " + name); - } + matrix = get_matrix(name, type); } template @@ -146,5 +186,21 @@ void EigenMatrixWriter::write_capacities(const std::vector& capacities) return; } - -} // namespace wmtk::utils \ No newline at end of file +template +Eigen::MatrixX EigenMatrixWriter::get_matrix(const std::string& name, const PrimitiveType type) const; +template +Eigen::MatrixX EigenMatrixWriter::get_matrix(const std::string& name, const PrimitiveType type) const; +template +Eigen::MatrixX EigenMatrixWriter::get_matrix(const std::string& name, const PrimitiveType type) const; +template +Eigen::MatrixX EigenMatrixWriter::get_matrix(const std::string& name, const PrimitiveType type) const; + +template +bool EigenMatrixWriter::has_matrix(const std::string& name, const PrimitiveType type) const; +template +bool EigenMatrixWriter::has_matrix(const std::string& name, const PrimitiveType type) const; +template +bool EigenMatrixWriter::has_matrix(const std::string& name, const PrimitiveType type) const; +template +bool EigenMatrixWriter::has_matrix(const std::string& name, const PrimitiveType type) const; +} // namespace wmtk::utils diff --git a/src/wmtk/utils/EigenMatrixWriter.hpp b/src/wmtk/utils/EigenMatrixWriter.hpp index 5304d08999..ec940b0fb8 100644 --- a/src/wmtk/utils/EigenMatrixWriter.hpp +++ b/src/wmtk/utils/EigenMatrixWriter.hpp @@ -18,6 +18,17 @@ class EigenMatrixWriter : public MeshWriter void get_FV_matrix(MatrixX& matrix); void get_EV_matrix(MatrixX& matrix); + MatrixXl get_TV_matrix() const; + MatrixXl get_FV_matrix() const; + MatrixXl get_EV_matrix() const; + + // automatically detects TV/FV/EV by looking for the largets one + MatrixXl get_simplex_vertex_matrix() const; + + template + bool has_matrix(const std::string& name, const PrimitiveType type) const; + template + Eigen::MatrixX get_matrix(const std::string& name, const PrimitiveType type) const; void get_double_matrix(const std::string& name, const PrimitiveType type, MatrixX& matrix); void diff --git a/src/wmtk/utils/internal/IndexSimplexMapper.cpp b/src/wmtk/utils/internal/IndexSimplexMapper.cpp new file mode 100644 index 0000000000..e172de7930 --- /dev/null +++ b/src/wmtk/utils/internal/IndexSimplexMapper.cpp @@ -0,0 +1,253 @@ +#include "IndexSimplexMapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wmtk::utils::internal { +namespace { +template +std::vector> get_simplices(std::array s) +{ + static_assert(E <= D); + std::set> F; + + std::array r; + + // extract the first E elements of each permutation of s + std::sort(s.begin(), s.end()); + do { + std::copy(s.begin(), s.begin() + E, r.begin()); + std::sort(r.begin(), r.end()); + // append to the simplex set + F.emplace(r); + } while (std::next_permutation(s.begin(), s.end())); + return std::vector>{F.begin(), F.end()}; +} +template +std::vector> get_simplices(const std::vector>& S) +{ + static_assert(E <= D); + std::set> F; + + // go through every simplex + for (const std::array& s : S) { + auto ss = get_simplices(s); + std::copy(ss.begin(), ss.end(), std::inserter(F, F.end())); + } + return std::vector>{F.begin(), F.end()}; +} + +template +std::vector> from_eigen(Eigen::Ref>& S) +{ + std::vector> r(S.rows()); + for (int j = 0; j < S.rows(); ++j) { + Eigen::Map>(r[j].data()) = S.row(j); + } + return r; +} +auto get_simplices(const Mesh& m) +{ + EigenMatrixWriter writer; + m.serialize(writer); + return writer.get_simplex_vertex_matrix(); +} + +} // namespace + +IndexSimplexMapper::IndexSimplexMapper(const Mesh& mesh) + : IndexSimplexMapper(get_simplices(mesh)) +{} +IndexSimplexMapper::IndexSimplexMapper(Eigen::Ref S) +{ + switch (S.cols()) { + case 2: initialize_edge_mesh(S); break; + case 3: initialize_tri_mesh(S); break; + case 4: initialize_tet_mesh(S); break; + default: assert(false); break; + } +} + +int64_t IndexSimplexMapper::id(const simplex::IdSimplex& id) +{ + return id.index(); +} +void IndexSimplexMapper::initialize_edge_mesh(Eigen::Ref S) +{ + m_E = from_eigen<2>(S); + m_E_map = make_map<2>(m_E); + m_V_map = make_child_map<2, 1>(m_E); + update_simplices(); +} +void IndexSimplexMapper::initialize_tri_mesh(Eigen::Ref S) +{ + m_F = from_eigen<3>(S); + m_F_map = make_map<3>(m_F); + m_E_map = make_child_map<3, 2>(m_F); + m_V_map = make_child_map<3, 1>(m_F); + update_simplices(); +} +void IndexSimplexMapper::initialize_tet_mesh(Eigen::Ref S) +{ + m_T = from_eigen<4>(S); + m_T_map = make_map<4>(m_T); + m_F_map = make_child_map<4, 3>(m_T); + m_E_map = make_child_map<4, 2>(m_T); + m_V_map = make_child_map<4, 1>(m_T); + + update_simplices(); +} +template +std::map, int64_t> IndexSimplexMapper::make_child_map( + std::vector> S) +{ + auto C = get_simplices(S); + return make_map(C); +} +template +std::map, int64_t> IndexSimplexMapper::make_map( + std::vector> S) +{ + std::map, int64_t> mp; + for (auto a : S) { + std::sort(a.begin(), a.end()); + size_t cur_size = mp.size(); + // std::map says if element already existed no element is added + mp.emplace(a, cur_size); + } + return mp; +} +void IndexSimplexMapper::update_simplices() +{ + auto update = [](const auto& mp, auto& vec) { + vec.resize(mp.size()); + + for (const auto& [arr, ind] : mp) { + vec[ind] = arr; + } + }; + + update(m_V_map, m_V); + update(m_E_map, m_E); + update(m_F_map, m_F); + update(m_T_map, m_T); +} + +template +int64_t IndexSimplexMapper::get_index( + std::array s, + const std::map, int64_t>& mp) +{ + std::sort(s.begin(), s.end()); + return mp.at(s); +} +template +int64_t get_index( + Eigen::Ref> s, + const std::map, int64_t>& mp) +{ + std::array a; + Eigen::Map>(a.data()) = s; + return get_index(a, mp); +} + +template +int64_t IndexSimplexMapper::get_index(const std::array& s) const +{ + return get_index(s, simplex_map()); +} + +template +std::vector IndexSimplexMapper::faces(size_t index) const +{ + if constexpr (Dim < ChildDim) { + return {}; + } else { + auto s = simplices().at(index); + auto faces = get_simplices(s); + std::vector r; + r.reserve(faces.size()); + std::transform( + faces.begin(), + faces.end(), + std::back_inserter(r), + [&](const std::array& d) { + return simplex_map().at(d); + }); + return r; + } +} +std::vector IndexSimplexMapper::faces(size_t index, int8_t dim, int8_t child_dim) const +{ + auto run = [&](const auto& d) -> std::vector { + using D = std::decay_t; + + constexpr static int Dim = D::value; + switch (child_dim) { + case 0: return faces(index); + case 1: return faces(index); + case 2: return faces(index); + case 3: return faces(index); + default: assert(false); return {}; + } + }; + switch (dim) { + case 0: return run(std::integral_constant{}); + case 1: return run(std::integral_constant{}); + case 2: return run(std::integral_constant{}); + case 3: return run(std::integral_constant{}); + default: assert(false); return {}; + } +} + +template +const std::map, int64_t>& IndexSimplexMapper::simplex_map() const +{ + if constexpr (D == 0) { + return m_V_map; + } else if constexpr (D == 1) { + return m_E_map; + } else if constexpr (D == 2) { + return m_F_map; + } else if constexpr (D == 3) { + return m_T_map; + } +} +template +const std::vector>& IndexSimplexMapper::simplices() const +{ + if constexpr (D == 0) { + return m_V; + } else if constexpr (D == 1) { + return m_E; + } else if constexpr (D == 2) { + return m_F; + } else if constexpr (D == 3) { + return m_T; + } +} + +template int64_t IndexSimplexMapper::get_index<0>(const std::array& s) const; +template int64_t IndexSimplexMapper::get_index<1>(const std::array& s) const; +template int64_t IndexSimplexMapper::get_index<2>(const std::array& s) const; +template int64_t IndexSimplexMapper::get_index<3>(const std::array& s) const; + +template const std::map, int64_t>& IndexSimplexMapper::simplex_map<0>() + const; +template const std::vector>& IndexSimplexMapper::simplices<0>() const; + +template const std::map, int64_t>& IndexSimplexMapper::simplex_map<1>() + const; +template const std::vector>& IndexSimplexMapper::simplices<1>() const; +template const std::map, int64_t>& IndexSimplexMapper::simplex_map<2>() + const; +template const std::vector>& IndexSimplexMapper::simplices<2>() const; +template const std::map, int64_t>& IndexSimplexMapper::simplex_map<3>() + const; +template const std::vector>& IndexSimplexMapper::simplices<3>() const; +} // namespace wmtk::utils::internal diff --git a/src/wmtk/utils/internal/IndexSimplexMapper.hpp b/src/wmtk/utils/internal/IndexSimplexMapper.hpp new file mode 100644 index 0000000000..5dd8f8b8c8 --- /dev/null +++ b/src/wmtk/utils/internal/IndexSimplexMapper.hpp @@ -0,0 +1,72 @@ +#pragma once +#include +#include + +namespace wmtk { +class Mesh; +namespace simplex { +class IdSimplex; +} +} // namespace wmtk +namespace wmtk::utils::internal { +class IndexSimplexMapper +{ +public: + IndexSimplexMapper(Eigen::Ref S); + IndexSimplexMapper(const Mesh& mesh); + + + // TOOD: this is just an array of size nCr, but easier to just say vector for now + template + std::vector faces(size_t index) const; + + std::vector faces(size_t index, int8_t dim, int8_t child_dim) const; + + static int64_t id(const simplex::IdSimplex& s); + +private: + void initialize_edge_mesh(Eigen::Ref S); + void initialize_tri_mesh(Eigen::Ref S); + void initialize_tet_mesh(Eigen::Ref S); + + + template + static int64_t get_index( + std::array s, + const std::map, int64_t>& mp); + template + static int64_t get_index( + Eigen::Ref> S, + const std::map, int64_t>& mp); + + template + static std::map, int64_t> make_child_map( + std::vector> S); + template + static std::map, int64_t> make_map( + std::vector> S); + + + std::map, int64_t> m_V_map; + std::map, int64_t> m_E_map; + std::map, int64_t> m_F_map; + std::map, int64_t> m_T_map; + + + void update_simplices(); + std::vector> m_V; + std::vector> m_E; + std::vector> m_F; + std::vector> m_T; + + +public: + template + const std::map, int64_t>& simplex_map() const; + template + const std::vector>& simplices() const; + + template + int64_t get_index(const std::array& s) const; +}; +} // namespace wmtk::utils::internal diff --git a/src/wmtk/utils/verify_simplex_index_valences.cpp b/src/wmtk/utils/verify_simplex_index_valences.cpp new file mode 100644 index 0000000000..f3c59a4a84 --- /dev/null +++ b/src/wmtk/utils/verify_simplex_index_valences.cpp @@ -0,0 +1,150 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal/IndexSimplexMapper.hpp" +namespace wmtk::utils { +namespace { + +template +std::array +indices(const Mesh& m, const internal::IndexSimplexMapper& mapper, const simplex::Simplex& s) +{ + assert(Dim == get_primitive_type_id(s.primitive_type())); + + std::array r; + if (s.primitive_type() == PrimitiveType::Vertex) { + simplex::IdSimplex id = m.get_id_simplex(s); + r[0] = mapper.id(id); + + } else { + auto simps = simplex::faces_single_dimension(m, s, PrimitiveType::Vertex); + auto cof = simps.simplex_vector(PrimitiveType::Vertex); + assert(cof.size() == Dim + 1); + for (size_t j = 0; j < Dim + 1; ++j) { + simplex::IdSimplex id = m.get_id_simplex(cof[j]); + r[j] = mapper.id(id); + } + } + + return r; +} +template +bool verify_simplex_index_valences(const Mesh& m, const internal::IndexSimplexMapper& mapper) +{ + constexpr static int meshD = get_primitive_type_id(mesh_pt); + constexpr static int D = get_primitive_type_id(pt); + + + std::vector> cofaces(mapper.simplices().size()); + for (size_t j = 0; j < mapper.simplices().size(); ++j) { + for (const auto& face_index : mapper.faces(j)) { + cofaces[face_index].emplace(j); + } + } + + if (mesh_pt == pt + 1) { + for (size_t j = 0; j < cofaces.size(); ++j) { + const auto& cof = cofaces[j]; + if (cof.size() > 2) { + wmtk::logger().warn(fmt::format( + "More than 2 {}-cofaces (facet indices={}) for a boundary {}-simplex [{}]", + D, + fmt::join(cof, ","), + meshD, + fmt::join(mapper.simplices()[j], ","))); + return false; + } + } + } + + + for (const Tuple& t : m.get_all(pt)) { + simplex::Simplex s(pt, t); + + auto cof = simplex::cofaces_single_dimension(m, s, mesh_pt).simplex_vector(); + + std::array i = indices(m, mapper, s); + const auto& cof2 = cofaces[mapper.get_index(i)]; + wmtk::logger().debug("Looking at {}-simplex {} on a {}-mesh", D, fmt::join(i, ","), meshD); + + + if (cof.size() != cof2.size()) { + wmtk::logger().warn( + "Cofaces size mismatch on {}-simplex {}, {}-mesh got (len{}), indices got [{}] " + "(len{})", + D, + fmt::join(i, ","), + meshD, + cof.size(), + fmt::join(cof2, ","), + cof2.size()); + return false; + } + for (const simplex::Simplex& facet : cof) { + assert(facet.primitive_type() == mesh_pt); + + simplex::IdSimplex id = m.get_id_simplex(facet); + int64_t index = mapper.id(id); + + if (cof2.find(index) == cof2.end()) { + wmtk::logger().warn( + "Cofaces mismatch on simplex {}, mesh simplex {} was not found in indices list " + " [{}] " + "(len{})", + fmt::join(i, ","), + index, + fmt::join(cof2, ","), + cof2.size()); + return false; + } + } + } + return true; +} +} // namespace + +bool verify_simplex_index_valences(const Mesh& m) +{ + internal::IndexSimplexMapper mapper(m); + + + PrimitiveType top = m.top_simplex_type(); + auto run = [&](const auto& d) -> bool { + using D = std::decay_t; + + constexpr static PrimitiveType pt = D::value; + bool ok = true; + if constexpr (pt >= PrimitiveType::Vertex) { + ok &= verify_simplex_index_valences(m, mapper); + } + if constexpr (pt >= PrimitiveType::Edge) { + ok &= verify_simplex_index_valences(m, mapper); + } + if constexpr (pt >= PrimitiveType::Triangle) { + ok &= verify_simplex_index_valences(m, mapper); + } + if constexpr (pt >= PrimitiveType::Tetrahedron) { + ok &= verify_simplex_index_valences(m, mapper); + } + return ok; + }; + switch (top) { + case PrimitiveType::Vertex: + return true; + // return run(std::integral_constant{}); + case PrimitiveType::Edge: + return run(std::integral_constant{}); + case PrimitiveType::Triangle: + return run(std::integral_constant{}); + case PrimitiveType::Tetrahedron: + return run(std::integral_constant{}); + default: assert(false); return {}; + } +} +} // namespace wmtk::utils diff --git a/src/wmtk/utils/verify_simplex_index_valences.hpp b/src/wmtk/utils/verify_simplex_index_valences.hpp new file mode 100644 index 0000000000..82f52ae413 --- /dev/null +++ b/src/wmtk/utils/verify_simplex_index_valences.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace wmtk { +class Mesh; +} +namespace wmtk::utils { +bool verify_simplex_index_valences(const Mesh& m); +} diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 2c16c90c99..939de356f9 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -8,5 +8,7 @@ set(TEST_SOURCES test_rational.cpp random.cpp test_dynamic_array.cpp + disjoint_set.cpp + valence_checker.cpp ) target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/utils/disjoint_set.cpp b/tests/utils/disjoint_set.cpp new file mode 100644 index 0000000000..e4d63ec349 --- /dev/null +++ b/tests/utils/disjoint_set.cpp @@ -0,0 +1,35 @@ +#include +#include + + +TEST_CASE("disjoint_set", "[disjoint_set]") +{ + wmtk::utils::DisjointSet ds(5); + + CHECK(ds.roots() == std::vector{0,1,2,3,4}); + + ds.merge(0,3); + CHECK(ds.roots() == std::vector{0,1,2,4}); + + CHECK(ds.get_root(0) == 0); + CHECK(ds.get_root(1) == 1); + CHECK(ds.get_root(2) == 2); + CHECK(ds.get_root(3) == 0); + CHECK(ds.get_root(4) == 4); + + ds.merge(2,3); + CHECK(ds.roots() == std::vector{0,1,4}); + CHECK(ds.get_root(0) == 0); + CHECK(ds.get_root(1) == 1); + CHECK(ds.get_root(2) == 0); + CHECK(ds.get_root(3) == 0); + CHECK(ds.get_root(4) == 4); + + ds.merge(4,1); + CHECK(ds.roots() == std::vector{0,1}); + CHECK(ds.get_root(0) == 0); + CHECK(ds.get_root(1) == 1); + CHECK(ds.get_root(2) == 0); + CHECK(ds.get_root(3) == 0); + CHECK(ds.get_root(4) == 1); +} diff --git a/tests/utils/valence_checker.cpp b/tests/utils/valence_checker.cpp new file mode 100644 index 0000000000..2aa4f6f8a7 --- /dev/null +++ b/tests/utils/valence_checker.cpp @@ -0,0 +1,108 @@ +#include +#include +#include + +#include +#include +using namespace wmtk; + +TEST_CASE("simplex_valences", "[simplex_valences]") +{ + { + // single tri + wmtk::TriMesh t; + RowVectors3l tris; + tris.resize(1, 3); + tris.row(0) << 0, 1, 2; + + t.initialize(tris); + CHECK(wmtk::utils::verify_simplex_index_valences(t)); + } + { + // manifold edg emeet + wmtk::TriMesh t; + RowVectors3l tris; + tris.resize(2, 3); + tris.row(0) << 0, 1, 2; + tris.row(1) << 0, 2, 3; + + t.initialize(tris); + CHECK(wmtk::utils::verify_simplex_index_valences(t)); + } + { + // duplicated simplex + wmtk::TriMesh t; + RowVectors3l tris; + tris.resize(2, 3); + tris.row(0) << 0, 1, 2; + tris.row(1) << 0, 2, 1; + + t.initialize(tris); + CHECK(!wmtk::utils::verify_simplex_index_valences(t)); + } + { + // nonmanifold vertex + wmtk::TriMesh t; + RowVectors3l tris; + tris.resize(2, 3); + tris.row(0) << 0, 1, 2; + tris.row(1) << 0, 3, 4; + t.initialize(tris); + CHECK(!wmtk::utils::verify_simplex_index_valences(t)); + } + + { + // single tet + wmtk::TetMesh t; + RowVectors4l tets; + tets.resize(1, 4); + tets.row(0) << 0, 1, 2, 3; + t.initialize(tets); + + CHECK(wmtk::utils::verify_simplex_index_valences(t)); + } + { + // duplicated simplex + wmtk::TetMesh t; + RowVectors4l tets; + tets.resize(2, 4); + tets.row(0) << 0, 1, 2, 3; + tets.row(1) << 0, 1, 3, 2; + t.initialize(tets); + + CHECK(!wmtk::utils::verify_simplex_index_valences(t)); + } + { + // manifold face meet + wmtk::TetMesh t; + RowVectors4l tets; + tets.resize(2, 4); + tets.row(0) << 0, 1, 2, 3; + tets.row(1) << 0, 1, 2, 4; + t.initialize(tets); + + CHECK(wmtk::utils::verify_simplex_index_valences(t)); + } + { + // edge nonmanifold + wmtk::TetMesh t; + RowVectors4l tets; + tets.resize(2, 4); + tets.row(0) << 0, 1, 2, 3; + tets.row(1) << 0, 1, 5, 4; + t.initialize(tets); + + CHECK(!wmtk::utils::verify_simplex_index_valences(t)); + } + { + // vertex nonmanifolld + wmtk::TetMesh t; + RowVectors4l tets; + tets.resize(2, 4); + tets.row(0) << 0, 1, 2, 3; + tets.row(1) << 0, 5, 6, 4; + t.initialize(tets); + + CHECK(!wmtk::utils::verify_simplex_index_valences(t)); + } +} From 65d9da53680756a2c1926c88fa9d18d55e0fa78f Mon Sep 17 00:00:00 2001 From: Michael Tao Date: Mon, 16 Dec 2024 14:13:41 -0500 Subject: [PATCH 11/11] making idsimplex friend --- src/wmtk/simplex/IdSimplex.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wmtk/simplex/IdSimplex.hpp b/src/wmtk/simplex/IdSimplex.hpp index 24c38ca79d..cd235c15b8 100644 --- a/src/wmtk/simplex/IdSimplex.hpp +++ b/src/wmtk/simplex/IdSimplex.hpp @@ -6,6 +6,9 @@ namespace wmtk { class Mesh; class Tuple; +namespace utils::internal { + class IndexSimplexMapper; +} } // namespace wmtk namespace wmtk::simplex { @@ -16,6 +19,7 @@ class IdSimplex public: friend class wmtk::Mesh; friend class NavigatableSimplex; + friend class wmtk::utils::internal::IndexSimplexMapper; IdSimplex() = default; IdSimplex(const NavigatableSimplex& s); bool valid() const { return m_index == -1; }