diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a5aac7817..ac91742957 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,10 @@ else() target_compile_features(wildmeshing_toolkit PUBLIC cxx_std_17) endif() +option(WMTK_MTAO_CONSTANTLY_VERIFY_MESH "verify mesh" OFF) +if(WMTK_MTAO_CONSTANTLY_VERIFY_MESH) + target_compile_options(wildmeshing_toolkit PUBLIC -DMTAO_CONSTANTLY_VERIFY_MESH) +endif() #target_compile_options(wildmeshing_toolkit PUBLIC -fconcepts) # the max dimension an attribute can be - default is to be any size (a dynamic size), but this can be set to a number like 6 diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt index 35ee362ddc..208ca730bf 100644 --- a/applications/CMakeLists.txt +++ b/applications/CMakeLists.txt @@ -26,7 +26,7 @@ add_application(delaunay ON) add_application(marching ON) add_application(procedural ON) add_application(multimesh OFF) -add_application(isotropic_remeshing OFF) +add_application(isotropic_remeshing ON) add_application(tetwild_simplification ON) add_application(triwild ON) add_application(tetwild ON) diff --git a/applications/cmake/wmtk_generate_test_config.cmake b/applications/cmake/wmtk_generate_test_config.cmake index c09daf2eb5..57d2995789 100644 --- a/applications/cmake/wmtk_generate_test_config.cmake +++ b/applications/cmake/wmtk_generate_test_config.cmake @@ -1,11 +1,12 @@ function(wmtk_generate_test_config CFG) - FILE(WRITE ${CMAKE_BINARY_DIR}/test_config.json "{\n") - - + set(FILE_CONTENTS "{\n") foreach(TEST_CONFIG ${CFG}) - FILE(APPEND ${CMAKE_BINARY_DIR}/test_config.json "${TEST_CONFIG},\n") + string(APPEND FILE_CONTENTS "${TEST_CONFIG},\n") endforeach() - FILE(APPEND ${CMAKE_BINARY_DIR}/test_config.json "\"skip\":{}}\n") + string(APPEND FILE_CONTENTS "\"skip\":{}}\n") + FILE(WRITE ${CMAKE_BINARY_DIR}/test_config.json "${FILE_CONTENTS}") endfunction() + + diff --git a/applications/cmake/wmtk_register_integration_test.cmake b/applications/cmake/wmtk_register_integration_test.cmake index c8c4256e9d..96bac0f959 100644 --- a/applications/cmake/wmtk_register_integration_test.cmake +++ b/applications/cmake/wmtk_register_integration_test.cmake @@ -31,6 +31,8 @@ macro(wmtk_register_integration_test ) \"extra_flags\":\"${_EXTRA_ARGUMENTS}\" }") + set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_CONFIG_PATH}") + SET(WMTK_TEST_CONFIG ${WMTK_TEST_CONFIG} PARENT_SCOPE) SET(WMTK_APPLICATION_TEST_NAMES ${WMTK_APPLICATION_TEST_NAMES} PARENT_SCOPE) endmacro() diff --git a/applications/convert/CMakeLists.txt b/applications/convert/CMakeLists.txt index ed5933a236..99beca3a80 100644 --- a/applications/convert/CMakeLists.txt +++ b/applications/convert/CMakeLists.txt @@ -21,5 +21,5 @@ wmtk_register_integration_test( CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR}/examples GIT_REPOSITORY "https://github.com/wildmeshing/data.git" GIT_TAG 363f8e860673a4e4f68df6465b99e86809c96283 - #EXTRA_ARGUMENTS run + EXTRA_ARGUMENTS json ) diff --git a/applications/convert/main.cpp b/applications/convert/main.cpp index 3fb759ff58..41e5f0631a 100644 --- a/applications/convert/main.cpp +++ b/applications/convert/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -18,8 +19,11 @@ #include "CLI/CLI.hpp" #include "wmtk/components/multimesh/MeshCollection.hpp" +#include "wmtk/components/multimesh/axis_aligned_fusion.hpp" #include "wmtk/components/multimesh/from_boundary.hpp" #include "wmtk/components/multimesh/from_facet_bijection.hpp" +#include "wmtk/components/multimesh/utils/AttributeDescription.hpp" +#include "wmtk/components/multimesh/utils/get_attribute.hpp" #include "wmtk/components/utils/PathResolver.hpp" using namespace wmtk::components; @@ -74,6 +78,29 @@ std::shared_ptr merge_meshes( parent_named_mesh.append_child_mesh_names(parent_mesh, child_named_mesh); return parent_mesh.shared_from_this(); + } else if (type == "axis_aligned_periodic") { + std::string position_attr_path = child_datas["position_attribute"]; + wmtk::components::multimesh::utils::AttributeDescription ad; + ad.path = position_attr_path; + auto mah = wmtk::components::multimesh::utils::get_attribute(mc, ad); + + std::vector mask = child_datas["axes"]; + std::string output_mesh_name = child_datas["fused_mesh_name"]; + std::optional eps; + if (child_datas.contains("epsilon")) { + eps = child_datas["epsilon"].get(); + } + if (!eps.has_value()) { + eps = 1e-10; + } + auto mptr = components::multimesh::axis_aligned_fusion(mah, mask, eps.value()); + const auto& nmm = mc.get_named_multimesh(position_attr_path); + + nlohmann::json jsout; + jsout[output_mesh_name] = *nmm.get_names_json(); + mc.add_mesh(wmtk::components::multimesh::NamedMultiMesh(*mptr, jsout)); + + return mptr; } } } @@ -81,25 +108,27 @@ std::shared_ptr merge_meshes( } } // namespace -int run(const fs::path& config_path /*, const std::optional& name_spec_file*/) +int run_js( + const std::string_view& app_name, + const nlohmann::json& j, + const std::optional& name_spec_file, + const std::optional& integration_test_config_file) { - nlohmann::json j; - { - std::ifstream ifs(config_path); - j = nlohmann::json::parse(ifs); - // if (name_spec_file.has_value()) { - // j["name"] = nlohmann::json::parse(std::ifstream(name_spec_file.value())); - // } - } - - spdlog::warn("{}", j.dump(2)); - + // if (name_spec_file.has_value()) { + // j["name"] = nlohmann::json::parse(std::ifstream(name_spec_file.value())); + // } wmtk::components::multimesh::MeshCollection meshes; components::utils::PathResolver path_resolver; if (j.contains("root")) { path_resolver = j["root"]; } + if (integration_test_config_file.has_value()) { + auto path = wmtk::applications::utils::get_integration_test_data_root( + integration_test_config_file.value(), + app_name); + path_resolver.add_path(path); + } std::shared_ptr output_mesh; if (j["input"].is_array()) { @@ -120,29 +149,22 @@ int run(const fs::path& config_path /*, const std::optional& name_spec if (!j.contains("output")) { wmtk::logger().info("convert: No output path provided"); - } else if (j["output"].is_object()) { - for (const auto& [mesh_path, out_opts_js] : j["output"].items()) { - auto opts = out_opts_js.get(); - - wmtk::components::output::output(meshes.get_mesh(mesh_path), opts); - } } else { - auto opts = j["output"].get(); - wmtk::components::output::output(*output_mesh, opts); + std::map output_opts = j["output"]; + wmtk::components::output::output(meshes, output_opts); } if (j.contains("report")) { + nlohmann::json jnew = j; const std::string report = j["report"]; meshes.make_canonical(); if (!report.empty()) { nlohmann::json out_json; auto& stats = out_json["stats"]; - for (const auto& [name, mesh] : meshes.all_meshes()) { - stats[name] = wmtk::applications::utils::element_count_report_named(mesh); - } - j.erase("report"); - out_json["input"] = j; + stats = wmtk::applications::utils::element_count_report_named(meshes); + jnew.erase("report"); + out_json["input"] = jnew; std::ofstream ofs(report); @@ -152,6 +174,18 @@ int run(const fs::path& config_path /*, const std::optional& name_spec return 0; } +int run( + const std::string_view& app_name, + const fs::path& config_path, + const std::optional& name_spec_file, + const std::optional& integration_test_config_file) +{ + nlohmann::json j; + std::ifstream ifs(config_path); + j = nlohmann::json::parse(ifs); + + return run_js(app_name, j, name_spec_file, integration_test_config_file); +} int main(int argc, char* argv[]) { @@ -160,21 +194,50 @@ int main(int argc, char* argv[]) app.ignore_case(); fs::path json_input_file; + std::optional json_integration_config_file; + std::optional json_integration_app_name; std::optional name_spec_file; - CLI::App* run_cmd; // = app.add_subcommand("run", "Run application"); - run_cmd = &app; - run_cmd->add_option("-j, --json", json_input_file, "json specification file") + auto add_it_path = [&](CLI::App& a) { + a.add_option( + "-c, --integration-test-config", + json_integration_config_file, + "Test config file for integration test") + ->check(CLI::ExistingFile); + a.add_option( + "-a, --integration-test-app", + json_integration_config_file, + "Test config file for integration test") + ->check(CLI::ExistingFile); + }; + + CLI::App* json_cmd = app.add_subcommand("json", "Run application using a json"); + json_cmd->add_option("-j, --json", json_input_file, "json specification file") + ->required(true) + ->check(CLI::ExistingFile); + + add_it_path(*json_cmd); + + + fs::path input; + fs::path output; + std::string type; + CLI::App* run_cmd = app.add_subcommand("run", "Convert mesh to another type"); + run_cmd->add_option("-i, --input", input, "input file") ->required(true) ->check(CLI::ExistingFile); - // run_cmd->add_option("-n, --name_spec", name_spec_file, "json specification file") + run_cmd->add_option("-o, --output", output, "output file"); + run_cmd->add_option("-t, --type", type, "output file type, knows [vtu,hdf5]"); + add_it_path(*run_cmd); + + + // json_cmd->add_option("-n, --name_spec", name_spec_file, "json specification file") // ->check(CLI::ExistingFile); CLI11_PARSE(app, argc, argv); // someday may add other suboptions - assert(run_cmd->parsed()); // if (!json_input_file.has_value() && !fill_config_path.has_value()) { // wmtk::logger().error("An input json file with [-j] is required unless blank config " @@ -184,12 +247,30 @@ int main(int argc, char* argv[]) int exit_mode = -1; - // run_cmd->callback([&]() { + // json_cmd->callback([&]() { // spdlog::warn("YOW!"); // assert(json_input_file.has_value()); // exit_mode = run(json_input_file.value()); // }); - exit_mode = run(json_input_file /*, name_spec_file*/); + if (json_cmd->parsed()) { + exit_mode = run(argv[0], json_input_file, name_spec_file, json_integration_config_file); + } else { + wmtk::components::input::InputOptions in; + in.path = input; + wmtk::components::output::OutputOptions out; + out.path = output; + out.position_attribute = "vertices"; + if (!type.empty() && type[0] != '.') { + type = '.' + type; + } + out.type = type; + + nlohmann::json js; + js["input"] = in; + js["output"][""] = out; + + exit_mode = run_js(argv[0], js, name_spec_file, json_integration_config_file); + } assert(exit_mode != -1); // "Some subcommand should have updated the exit mode" diff --git a/applications/integration_test.py b/applications/integration_test.py index 407e35141b..0aa30cd85c 100644 --- a/applications/integration_test.py +++ b/applications/integration_test.py @@ -18,10 +18,12 @@ class IntegrationTest(unittest.TestCase): BINARY_FOLDER = fix_path(os.environ['WMTK_BINARY_FOLDER']) if "WMTK_BINARY_FOLDER" in os.environ else None CONFIG_FILE = fix_path(os.environ['WMTK_CONFIG_FILE']) if "WMTK_CONFIG_FILE" in os.environ else None - def __init__(self, name, test_config, configs_to_run = None, run_all = False): + # verbose = print stdout / stderr to the screen + def __init__(self, name, test_config, configs_to_run = None, run_all = False, verbose=False): super().__init__() self.working_dir_fp = tempfile.TemporaryDirectory() self.working_dir = self.working_dir_fp.name + self.verbose= verbose self.name = name self.test_config = test_config @@ -69,12 +71,15 @@ def tearDown(self): def execute_json(self, json_file_path): cmd = [self.executable] +self.extra_flags + [ "-j", json_file_path] - res = subprocess.run(cmd, cwd=self.working_dir, capture_output=True) + # capture input if we're not going to already print stuff + res = subprocess.run(cmd, cwd=self.working_dir, capture_output=not self.verbose) if res.returncode != 0: - print(f"Error running [{' '.join(cmd)}]", flush=True) - print(res.stderr.decode('utf-8'), flush=True) - print(res.stdout.decode('utf-8'), flush=True) + print(f"Error running [{' '.join(cmd)}] from working directory [{self.working_dir}]") + # in verbose mode the stderr/stdout buffers were already flushed + if not self.verbose: + print(res.stderr.decode('utf-8'), flush=True) + print(res.stdout.decode('utf-8'), flush=True) return res def get_root_path(self, input_js): @@ -171,21 +176,20 @@ def run_one(self, test_file): self.assertTrue(True) + def runTestFile(self, test_file_name): + with self.subTest(msg=f"{self.name}-{test_file_name}"): + print("Running test", test_file_name, flush=True) + test_file = os.path.join(self.config_folder, test_file_name) + print(f"Test file: {test_file}", flush=True) + self.run_one(test_file) + def runTest(self): for test_file_name in self.config["tests"]: - with self.subTest(msg=f"{self.name}-{test_file_name}"): - print("Running test", test_file_name, flush=True) - test_file = os.path.join(self.config_folder, test_file_name) - print(f"Test file: {test_file}", flush=True) - self.run_one(test_file) + self.runTestFile(test_file_name) if self.run_all and "release_only_tests" in self.config: for test_file_name in self.config["release_only_tests"]: - with self.subTest(msg=f"{self.name}-{test_file_name}"): - print("Running slow test", test_file_name, flush=True) - test_file = os.path.join(self.config_folder, test_file_name) - print(f"Test file: {test_file}", flush=True) - self.run_one(test_file) + self.runTestFile(test_file_name) def load_config_json(config_file): @@ -196,7 +200,7 @@ def load_config_json(config_file): del config["skip"] return config -def make_suite(config_file, single_application = None, single_config = None, run_all = False): +def make_suite(config_file, single_application = None, single_config = None, run_all = False, verbose=False): config = load_config_json(config_file) suite = unittest.TestSuite() @@ -206,7 +210,7 @@ def make_suite(config_file, single_application = None, single_config = None, run continue if single_application is None or key == single_application: # expects a list of configs to run - suite.addTest(IntegrationTest(key,value, None if single_config is None else [single_config], run_all)) + suite.addTest(IntegrationTest(key,value, None if single_config is None else [single_config], run_all, verbose=verbose)) return suite @@ -222,6 +226,7 @@ def make_suite(config_file, single_application = None, single_config = None, run parser.add_argument('-t', '--test-application', help="Runs tests for a single application") parser.add_argument('-s', '--test-script', help="Runs a particular test script") parser.add_argument('-a', '--all-tests', help="Runs all tests, including slow ones", action='store_true') + parser.add_argument("-v", '--verbose', help="print execution data verbosely", action='store_true') subparsers = parser.add_subparsers(help="subcommand help", dest="subcommand") create_parser = subparsers.add_parser(name="create",help="create integration test json") @@ -229,6 +234,7 @@ def make_suite(config_file, single_application = None, single_config = None, run create_parser.add_argument("-b", '--binary', help="Name of the binary being run") create_parser.add_argument("-i", '--input', help="input config") create_parser.add_argument("-o", '--output', help="output config filename, placed in config folder") + create_parser.add_argument("-v", '--verbose', help="print execution data verbosely", action='store_true') args = parser.parse_args() @@ -260,7 +266,7 @@ def make_suite(config_file, single_application = None, single_config = None, run # no subcommand chosen so we just run if args.subcommand is None: # test_application and test_script are None if not set - suite = make_suite(config_file, args.test_application, args.test_script, args.all_tests) + suite = make_suite(config_file, args.test_application, args.test_script, args.all_tests, verbose=args.verbose) runner = unittest.TextTestRunner() result = runner.run(suite) @@ -277,7 +283,7 @@ def make_suite(config_file, single_application = None, single_config = None, run config = load_config_json(config_file) binary = args.binary my_config = config[binary] - test = IntegrationTest(binary,my_config) + test = IntegrationTest(binary,my_config, verbose=args.verbose) test.create_reporter(args.input,args.output) pass diff --git a/applications/isotropic_remeshing/CMakeLists.txt b/applications/isotropic_remeshing/CMakeLists.txt index dcae7f54db..2cb27494ae 100644 --- a/applications/isotropic_remeshing/CMakeLists.txt +++ b/applications/isotropic_remeshing/CMakeLists.txt @@ -10,21 +10,16 @@ set(SHARED_STUFF ) -wmtk_add_application(isotropic_remeshing_3d_app - main_3d.cpp +wmtk_add_application(isotropic_remeshing_app + main.cpp #spec.hpp ${SHARED_STUFF} ) -wmtk_add_application(isotropic_remeshing_uv_app - main_uv.cpp - #spec.hpp - ${SHARED_STUFF} - ) # isotropic_remeshing requires the input component and the procedural component -target_link_libraries(isotropic_remeshing_3d_app PRIVATE +target_link_libraries(isotropic_remeshing_app PRIVATE wmtk::input wmtk::isotropic_remeshing wmtk::multimesh @@ -32,26 +27,20 @@ wmtk::output wmtk::application_utils ) -target_link_libraries(isotropic_remeshing_uv_app PRIVATE -wmtk::input -wmtk::isotropic_remeshing -wmtk::output -wmtk::multimesh -wmtk::application_utils -) - wmtk_register_integration_test( - EXEC_NAME isotropic_remeshing_3d_app - CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_config_3d.json + EXEC_NAME isotropic_remeshing_app + CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_config.json GIT_REPOSITORY "https://github.com/wildmeshing/data.git" - GIT_TAG 152a561697a6e923451ca8535309cbe1e116a9fa - CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR} -) + GIT_TAG + 363f8e860673a4e4f68df6465b99e86809c96283 + CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR}/examples -wmtk_register_integration_test( - EXEC_NAME isotropic_remeshing_uv_app - CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_config_uv.json - GIT_REPOSITORY "https://github.com/wildmeshing/data.git" - GIT_TAG 152a561697a6e923451ca8535309cbe1e116a9fa - CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR} ) +# +#wmtk_register_integration_test( +# EXEC_NAME isotropic_remeshing_uv_app +# CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_config_uv.json +# GIT_REPOSITORY "https://github.com/wildmeshing/data.git" +# GIT_TAG 152a561697a6e923451ca8535309cbe1e116a9fa +# CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR} +#) diff --git a/applications/isotropic_remeshing/examples/100071_sf.json b/applications/isotropic_remeshing/examples/100071_sf.json new file mode 100644 index 0000000000..69e3c76fbc --- /dev/null +++ b/applications/isotropic_remeshing/examples/100071_sf.json @@ -0,0 +1,30 @@ +{ + "input": { + "edge_swap_mode": "valence", + "fix_uv_seam": false, + "input": { + "file": "100071_sf.msh", + "name_spec": "main" + }, + "iterations": 5, + "length_abs": -1, + "length_rel": 0.01, + "lock_boundary": false, + "other_position_attributes": [ + "vertices" + ], + "output": { + "file": "100071_sf", + "type": ".vtu" + }, + "position_attribute": "vertices", + "use_for_periodic": false + }, + "stats": { + "main": { + "edges": 642213, + "faces": 428142, + "vertices": 214067 + } + } +} \ No newline at end of file diff --git a/applications/isotropic_remeshing/examples/bunny.json b/applications/isotropic_remeshing/examples/bunny.json new file mode 100644 index 0000000000..da9ded9d6a --- /dev/null +++ b/applications/isotropic_remeshing/examples/bunny.json @@ -0,0 +1,40 @@ +{ + "input": { + "file": "bunny_3d.msh", + "multimesh": { + "boundary": { + "transferred_attributes": [ + { + "name": "vertices", + "simplex": 0, + "type": "double" + } + ], + "tag_attribute": "is_boundary" + } + + }, + "name_spec": + { + "main": "boundary" + } + }, + "position_attribute": { + "mesh": "main.boundary", + "name": "vertices" + }, + "inversion_position_attribute": "vertices", + "other_position_attributes": [ + "vertices" + ], + "pass_through_attributes": [ + "is_boundary" + ], + "iterations": 30, + "length_rel": 0.05, + "length_abs": -1, + "use_for_periodic": false, + "dont_disable_split": false, + "fix_uv_seam": false, + "lock_boundary": false +} diff --git a/applications/isotropic_remeshing/examples/camel.json b/applications/isotropic_remeshing/examples/camel.json new file mode 100644 index 0000000000..34fc5e4300 --- /dev/null +++ b/applications/isotropic_remeshing/examples/camel.json @@ -0,0 +1,30 @@ +{ + "input": { + "dont_disable_split": false, + "edge_swap_mode": "valence", + "fix_uv_seam": false, + "input": { + "file": "unit_test/meshes/camel.msh", + "name_spec": { + "pos": "uv" + } + }, + "iterations": 5, + "length_abs": -1, + "length_rel": 0.01, + "lock_boundary": true, + "output": { + "path": "output.hdf5", + "type": ".hdf5" + }, + "position_attribute": "vertices", + "use_for_periodic": false + }, + "stats": { + "pos": { + "edges": 333069, + "faces": 221098, + "vertices": 111972 + } + } +} \ No newline at end of file diff --git a/applications/isotropic_remeshing/examples/periodic_2d.json b/applications/isotropic_remeshing/examples/periodic_2d.json new file mode 100644 index 0000000000..6f5c48e57f --- /dev/null +++ b/applications/isotropic_remeshing/examples/periodic_2d.json @@ -0,0 +1,27 @@ +{ + "input": { + "file": "periodic_data/0101_2_clean.msh", + "multimesh": { + "fusion": { + "fusion": [true,true] + } + }, + "name_spec": { + "periodic": { + "position" + } + } + }, + "inversion_position_attribute": { + "mesh": "periodic.position", + "name": "vertices" + }, + "position_attribute": { + "mesh": "periodic.position", + "name": "vertices" + }, + "iterations": 100, + "length_rel": 0.03, + "lock_boundary": true, + "use_for_periodic": true, +} diff --git a/applications/isotropic_remeshing/examples/raw/100071_sf.json b/applications/isotropic_remeshing/examples/raw/100071_sf.json new file mode 100644 index 0000000000..2b6cffd298 --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/100071_sf.json @@ -0,0 +1,22 @@ +{ + "input": { + "path": "100071_sf.msh", + "name_spec": + "main" + }, + "position_attribute": "vertices", + "other_position_attributes": [ + "vertices" + ], + "iterations": 5, + "length_rel": 0.01, + "length_abs": -1, + "use_for_periodic": false, + "edge_swap_mode": "valence", + "fix_uv_seam": false, + "lock_boundary": false, + "output": { + "path": "100071_sf", + "type": ".vtu" + } +} diff --git a/applications/isotropic_remeshing/examples/raw/camel.json b/applications/isotropic_remeshing/examples/raw/camel.json new file mode 100644 index 0000000000..5d177f5273 --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/camel.json @@ -0,0 +1,25 @@ +{ + "input": { + "path": "unit_test/meshes/camel.msh", + "name_spec": "pos" + }, + "position_attribute": "pos/vertices", + "iterations": 60, + "length_rel": 0.01, + "envelope_size": 1e-2, + "lock_boundary": true, + "edge_swap_mode": "valence", + "use_for_periodic": false, + "fix_uv_seam": true, + "output": { + "path": "camel.hdf5", + "type": ".hdf5" + }, + "intermediate_output_format": { + "pos": { + "path": "camel_{:04}", + "type": ".vtu", + "position_attribute": "vertices" + } + } +} diff --git a/applications/isotropic_remeshing/examples/raw/camel_uv.json b/applications/isotropic_remeshing/examples/raw/camel_uv.json new file mode 100644 index 0000000000..a038888f41 --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/camel_uv.json @@ -0,0 +1,32 @@ +{ + "input": { + "path": "multimesh/camel.hdf5", + "name_spec": {"pos": "uv"} + }, + "position_attributes": "pos.uv/vertices", + "other_position_attribute": "pos/vertices", + "iterations": 60, + "length_rel": 0.01, + "envelope_size": 1e-2, + "lock_boundary": true, + "edge_swap_mode": "valence", + "use_for_periodic": false, + "dont_disable_split": false, + "fix_uv_seam": true, + "output": { + "path": "camel.hdf5", + "type": ".hdf5" + }, + "intermediate_output_format": { + "pos": { + "path": "cameluv_{:04}", + "type": ".vtu", + "position_attribute": "vertices" + }, + "pos.uv": { + "path": "cameluv_uv_{:04}", + "type": ".vtu", + "position_attribute": "vertices" + } + } +} diff --git a/applications/isotropic_remeshing/examples/raw/periodic_2d.json b/applications/isotropic_remeshing/examples/raw/periodic_2d.json new file mode 100644 index 0000000000..6f5c48e57f --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/periodic_2d.json @@ -0,0 +1,27 @@ +{ + "input": { + "file": "periodic_data/0101_2_clean.msh", + "multimesh": { + "fusion": { + "fusion": [true,true] + } + }, + "name_spec": { + "periodic": { + "position" + } + } + }, + "inversion_position_attribute": { + "mesh": "periodic.position", + "name": "vertices" + }, + "position_attribute": { + "mesh": "periodic.position", + "name": "vertices" + }, + "iterations": 100, + "length_rel": 0.03, + "lock_boundary": true, + "use_for_periodic": true, +} diff --git a/applications/isotropic_remeshing/examples/raw/plane.json b/applications/isotropic_remeshing/examples/raw/plane.json new file mode 100644 index 0000000000..6e9dcb8521 --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/plane.json @@ -0,0 +1,31 @@ +{ + "input": { + "path": "plane_dense.hdf5", + "0path": "2d/4triangles.msh", + "name_spec": "pos" + }, + "use_split": false, + "use_collapse": false, + "0use_smooth": false, + "0use_swap": false, + "position_attribute": "pos/vertices", + "inversion_position_attribute": "pos/vertices", + "iterations": 5, + "length_rel": 0.05, + "envelope_size": 1e-2, + "lock_boundary": false, + "edge_swap_mode": "valence", + "use_for_periodic": false, + "fix_uv_seam": false, + "output": { + "path": "plane.hdf5", + "type": ".hdf5" + }, + "intermediate_output_format": { + "pos": { + "path": "plane_{:04}", + "type": ".vtu", + "position_attribute": "vertices" + } + } +} diff --git a/applications/isotropic_remeshing/examples/raw/small.json b/applications/isotropic_remeshing/examples/raw/small.json new file mode 100644 index 0000000000..ca4fdef0fe --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/small.json @@ -0,0 +1,30 @@ +{ + "input": { + "path": "unit_test/meshes/small3d.msh", + "validate": true, + "name_spec": + "main" + }, + "position_attribute": "vertices", + "other_position_attributes": [ + "vertices" + ], + "iterations": 100, + "length_rel": 0.2, + "use_for_periodic": false, + "edge_swap_mode": "valence", + "fix_uv_seam": false, + "lock_boundary": false, + "envelope_size": 1e-3, + "output": { + "path": "tetwild_fig8_mid", + "type": ".vtu" + }, + "intermediate_output_format": { + "main": { + "path": "tetwild_fig8_mid_{:04}", + "type": ".vtu", + "position_attribute": "vertices" + } + } +} diff --git a/applications/isotropic_remeshing/examples/raw/tetwild_fig8_mid.json b/applications/isotropic_remeshing/examples/raw/tetwild_fig8_mid.json new file mode 100644 index 0000000000..6ffa932bbc --- /dev/null +++ b/applications/isotropic_remeshing/examples/raw/tetwild_fig8_mid.json @@ -0,0 +1,19 @@ +{ + "input": { + "file": "tetwild_fig8_mid.msh", + "name_spec": + "main" + }, + "position_attribute": "vertices", + "iterations": 30, + "length_rel": 0.05, + "length_abs": -1, + "use_for_periodic": false, + "edge_swap_mode": "valence", + "fix_uv_seam": false, + "lock_boundary": false, + "output": { + "file": "tetwild_fig8_mid", + "type": ".vtu" + } +} diff --git a/applications/isotropic_remeshing/examples/small.json b/applications/isotropic_remeshing/examples/small.json new file mode 100644 index 0000000000..51c3255dda --- /dev/null +++ b/applications/isotropic_remeshing/examples/small.json @@ -0,0 +1,31 @@ +{ + "input": { + "edge_swap_mode": "skip", + "edge_swap_mode3": "valence", + "fix_uv_seam": false, + "input": { + "file": "unit_test/meshes/small3d.msh", + "name_spec": "main" + }, + "iterations": 10, + "length_abs": -1, + "length_rel": 0.2, + "lock_boundary": false, + "other_position_attributes": [ + "vertices" + ], + "output": { + "file": "tetwild_fig8_mid", + "type": ".vtu" + }, + "position_attribute": "vertices", + "use_for_periodic": false + }, + "stats": { + "main": { + "edges": 255504, + "faces": 169864, + "vertices": 85636 + } + } +} \ No newline at end of file diff --git a/applications/isotropic_remeshing/main.cpp b/applications/isotropic_remeshing/main.cpp new file mode 100644 index 0000000000..e2819fe04a --- /dev/null +++ b/applications/isotropic_remeshing/main.cpp @@ -0,0 +1,167 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wmtk/components/utils/PathResolver.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "make_multimesh.hpp" +#include "spec.hpp" + +using namespace wmtk::components; +using namespace wmtk; +namespace fs = std::filesystem; + +constexpr static std::string root_attribute_name = "root"; + +int main(int argc, char* argv[]) +{ + CLI::App app{argv[0]}; + + app.ignore_case(); + + fs::path json_input_file; + fs::path json_integration_config_file; + app.add_option("-j, --json", json_input_file, "json specification file") + ->required(true) + ->check(CLI::ExistingFile); + app.add_option( + "-i, --integration-test-config", + json_integration_config_file, + "Test config file for integration test") + ->check(CLI::ExistingFile); + CLI11_PARSE(app, argc, argv); + + // nlohmann::json j = wmtk::applications::utils::parse_jse( + // wmtk::applications::isotropic_remeshing::spec, + // json_input_file); + + spdlog::warn("File is {}", json_input_file.string()); + std::ifstream ifs(json_input_file); + nlohmann::json j = nlohmann::json::parse(ifs); + + const auto input_js = j["input"]; + components::utils::PathResolver path_resolver; + + if (j.contains(root_attribute_name)) { + path_resolver = j[root_attribute_name]; + } + if (!json_integration_config_file.empty()) { + auto path = wmtk::applications::utils::get_integration_test_data_root( + json_integration_config_file, + argv[0]); + path_resolver.add_path(path); + } + + + auto input_opts = input_js.get(); + + + wmtk::components::multimesh::MeshCollection mc; + + auto& named_mesh = mc.add_mesh(wmtk::components::input::input(input_opts, path_resolver)); + + auto mesh_ptr = named_mesh.root().shared_from_this(); + if (input_js.contains("multimesh")) { + mesh_ptr = make_multimesh(*mesh_ptr, input_js["multimesh"]); + + spdlog::info("{} children", mesh_ptr->get_all_child_meshes().size()); + } + + + if(!mc.is_valid()) { + wmtk::logger().error("Input mesh did not match the name specification, going to throw an exception to help debugging"); + mc.is_valid(true); + } + + wmtk::components::isotropic_remeshing::IsotropicRemeshingOptions options; + + options.load_json(j); + + options.position_attribute = + wmtk::components::multimesh::utils::get_attribute(mc, j["position_attribute"]); + + assert(options.position_attribute.is_valid()); + + if (j.contains("inversion_position_attribute")) { + options.inversion_position_attribute = wmtk::components::multimesh::utils::get_attribute( + mc, + j["inversion_position_attribute"]); + } + if (j.contains("other_position_attributes")) { + for (const auto& other : j["other_position_attributes"]) { + options.other_position_attributes.emplace_back( + wmtk::components::multimesh::utils::get_attribute(mc, other)); + } + } + if (j.contains("pass_through_attributes")) { + for (const auto& other : j["pass_through_attributes"]) { + options.pass_through_attributes.emplace_back( + wmtk::components::multimesh::utils::get_attribute(mc, other)); + } + } + for (const auto& attr : options.pass_through_attributes) { + spdlog::info("Pass through: {}", attr.name()); + } + + options.mesh_collection = &mc; + if(j.contains("intermediate_output_format")) { + options.intermediate_output_format = j["intermediate_output_format"]; + } + assert(options.position_attribute.is_valid()); + + + auto out_opts = j["output"].get(); + out_opts.position_attribute = options.position_attribute; + + + + + wmtk::components::isotropic_remeshing::isotropic_remeshing(options); + + // input uv mesh + + // multimesh the input meshes if not already multimeshed + // OR - should this be a diff app? + + // call isotropic_remeshing + + + wmtk::components::output::output(*mesh_ptr, out_opts); + + if (j.contains("report")) { + const std::string report = j["report"]; + mc.make_canonical(); + if (!report.empty()) { + nlohmann::json out_json; + auto& stats = out_json["stats"]; + stats = wmtk::applications::utils::element_count_report_named(mc); + j.erase("report"); + if (j.contains(root_attribute_name)) { + j.erase(root_attribute_name); + } + out_json["input"] = j; + + + std::ofstream ofs(report); + ofs << std::setw(2) << out_json; + } + } +} diff --git a/applications/isotropic_remeshing/main_3d.cpp b/applications/isotropic_remeshing/main_3d.cpp deleted file mode 100644 index 8ec1130fea..0000000000 --- a/applications/isotropic_remeshing/main_3d.cpp +++ /dev/null @@ -1,74 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include "spec.hpp" - -using namespace wmtk::components; -using namespace wmtk; -namespace fs = std::filesystem; - -int main(int argc, char* argv[]) -{ - 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 = wmtk::applications::utils::parse_jse( - wmtk::applications::isotropic_remeshing::spec, - json_input_file); - - const auto input_opts = j["input"].get(); - - auto named_mesh = wmtk::components::input::input(input_opts); - auto mesh_ptr = named_mesh.root().shared_from_this(); - - wmtk::components::isotropic_remeshing::IsotropicRemeshingOptions options; - - options.load_json(j); - - const auto& js_attrs = j["attributes"]; - options.position_attribute = - mesh_ptr->get_attribute_handle(js_attrs["position"], PrimitiveType::Vertex); - if (auto opt = js_attrs["position"]; !opt.empty()) { - options.inversion_position_attribute = - mesh_ptr->get_attribute_handle(opt, PrimitiveType::Vertex); - } - for (const auto& other : js_attrs["other_positions"]) { - options.inversion_position_attribute = - mesh_ptr->get_attribute_handle(other, PrimitiveType::Vertex); - } - - wmtk::components::isotropic_remeshing::isotropic_remeshing(options); - - // input uv mesh - - // multimesh the input meshes if not already multimeshed - // OR - should this be a diff app? - - // call isotropic_remeshing - - - const std::string output_path = j["output"]; - wmtk::components::output::output_hdf5(*mesh_ptr, j["output"]); -} diff --git a/applications/isotropic_remeshing/main_uv.cpp b/applications/isotropic_remeshing/main_uv.cpp deleted file mode 100644 index 04300153ca..0000000000 --- a/applications/isotropic_remeshing/main_uv.cpp +++ /dev/null @@ -1,103 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include "make_multimesh.hpp" -#include "spec.hpp" - -using namespace wmtk::components; -using namespace wmtk; -namespace fs = std::filesystem; - -int main(int argc, char* argv[]) -{ - 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 = wmtk::applications::utils::parse_jse( - // wmtk::applications::isotropic_remeshing::spec, - // json_input_file); - - std::ifstream ifs(json_input_file); - nlohmann::json j = nlohmann::json::parse(ifs); - - const auto input_js = j["input"]; - - const auto input_opts = input_js.get(); - - - wmtk::components::input::MeshCollection mc; - auto& named_mesh = mc.add_mesh(input_opts); - auto mesh_ptr = named_mesh.root().shared_from_this(); - spdlog::warn("Input js: {}", input_js.dump(2)); - if(input_js.contains("multimesh")) { - mesh_ptr = make_multimesh(*mesh_ptr, input_js["multimesh"]); - - spdlog::info("{} children", mesh_ptr->get_all_child_meshes().size()); - } - - - wmtk::components::isotropic_remeshing::IsotropicRemeshingOptions options; - - options.load_json(j); - - options.position_attribute = wmtk::components::input::utils::get_attribute( - mc, j["position_attribute"]); - - if (j.contains("inversion_position_attribute")) { - options.inversion_position_attribute = wmtk::components::input::utils::get_attribute( - mc, j["inversion_position_attribute"]); - } - if(j.contains("other_position_attributes")) { - for (const auto& other : j["other_position_attributes"]) { - options.other_position_attributes.emplace_back( - wmtk::components::input::utils::get_attribute( - mc, other)); - } - } - if(j.contains("pass_through_attributes")) { - for (const auto& other : j["pass_through_attributes"]) { - options.pass_through_attributes.emplace_back( - wmtk::components::input::utils::get_attribute( - mc, other)); - } - } - for(const auto& attr: options.pass_through_attributes) { - spdlog::info("Pass through: {}", attr.name()); - } - - wmtk::components::isotropic_remeshing::isotropic_remeshing(options); - - // input uv mesh - - // multimesh the input meshes if not already multimeshed - // OR - should this be a diff app? - - // call isotropic_remeshing - - - const std::string output_path = j["output"]; - wmtk::components::output::output(*mesh_ptr, j["output"]); -} diff --git a/applications/isotropic_remeshing/make_multimesh.cpp b/applications/isotropic_remeshing/make_multimesh.cpp index 9930ae4094..e8deac0392 100644 --- a/applications/isotropic_remeshing/make_multimesh.cpp +++ b/applications/isotropic_remeshing/make_multimesh.cpp @@ -7,7 +7,8 @@ #include #include -#include +#include +#include #include #include @@ -19,7 +20,7 @@ void make_boundary(wmtk::Mesh& m, const nlohmann::json& bdry_cfg) { std::vector transferred_attrs; if(bdry_cfg.contains("transferred_attributes")) { for(const nlohmann::json& attr: bdry_cfg["transferred_attributes"]) { - transferred_attrs.emplace_back(wmtk::components::input::utils::get_attribute(m, attr)); + transferred_attrs.emplace_back(wmtk::components::multimesh::utils::get_attribute(m, attr)); } } int dim = bdry_cfg.contains("simplex") ? bdry_cfg["simplex"].get() : m.top_cell_dimension() - 1; diff --git a/applications/isotropic_remeshing/test_config_3d.json b/applications/isotropic_remeshing/test_config.json similarity index 51% rename from applications/isotropic_remeshing/test_config_3d.json rename to applications/isotropic_remeshing/test_config.json index 767a505eec..9a6150793d 100644 --- a/applications/isotropic_remeshing/test_config_3d.json +++ b/applications/isotropic_remeshing/test_config.json @@ -1,7 +1,9 @@ { - "test_directory": "examples", + "test_directory": "", "tests": [ - "delaunay_out.json" + "100071_sf.json", + "small.json", + "tetwild_fig8_mid.json" ], "input_tag": "input", "oracle_tag": "report", diff --git a/applications/isotropic_remeshing/test_config_uv.json b/applications/isotropic_remeshing/test_config_uv.json deleted file mode 100644 index 767a505eec..0000000000 --- a/applications/isotropic_remeshing/test_config_uv.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "test_directory": "examples", - "tests": [ - "delaunay_out.json" - ], - "input_tag": "input", - "oracle_tag": "report", - "input_directory_tag": "root" -} diff --git a/applications/procedural/CMakeLists.txt b/applications/procedural/CMakeLists.txt index 2bdbe6e57a..43aa9df6fb 100644 --- a/applications/procedural/CMakeLists.txt +++ b/applications/procedural/CMakeLists.txt @@ -5,11 +5,11 @@ wmtk_add_application(procedural_app spec.hpp ) +target_compile_features(procedural_app PUBLIC cxx_std_20) #register_jse_json(APPLICATION_NAME procedural INPUT procedural_spec.json ) -# procedural requires the input component and the procedural component +# procedural requires the output component and the procedural component target_link_libraries(procedural_app PRIVATE -wmtk::input wmtk::procedural wmtk::output wmtk::application_utils @@ -19,4 +19,5 @@ wmtk_register_integration_test( EXEC_NAME procedural_app CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_config.json CONFIG_PATH ${CMAKE_CURRENT_SOURCE_DIR}/examples + EXTRA_ARGUMENTS run ) diff --git a/applications/procedural/examples/grid2_cyclic_example.json b/applications/procedural/examples/grid2_cyclic_example.json index 9d2945de27..ea50ddb857 100644 --- a/applications/procedural/examples/grid2_cyclic_example.json +++ b/applications/procedural/examples/grid2_cyclic_example.json @@ -1 +1,31 @@ -{"edges":300,"faces":200,"input":{"grid":{"coordinates":{"name":"vertices","spacing":[1,1]},"cycles":[true,true],"dimensions":[10,10],"tiling":"diagonal"},"output":{"attributes":{"position":"vertices"},"file":"grid2d_cyclic.hdf5"}},"vertices":100} \ No newline at end of file +{ + "edges": 300, + "faces": 200, + "input": { + "grid": { + "coordinates": { + "name": "vertices", + "spacing": [ + 1, + 1 + ] + }, + "cycles": [ + true, + true + ], + "dimensions": [ + 10, + 10 + ], + "tiling": "diagonal" + }, + "output": { + "attributes": { + "position": "vertices" + }, + "file": "grid2d_cyclic.hdf5" + } + }, + "vertices": 100 +} diff --git a/applications/procedural/examples/grid2_cyclic_positions.json b/applications/procedural/examples/grid2_cyclic_positions.json index d1e70183f6..a10468c342 100644 --- a/applications/procedural/examples/grid2_cyclic_positions.json +++ b/applications/procedural/examples/grid2_cyclic_positions.json @@ -1,19 +1,19 @@ { - "grid": { - "cycles": [ - true, - true - ], - "dimensions": [ - 10, - 10 - ], - "tiling": "diagonal" + "grid": { + "cycles": [ + true, + true + ], + "dimensions": [ + 10, + 10 + ], + "tiling": "diagonal" + }, + "output": { + "attributes": { + "position": "vertices" }, - "output": { - "attributes": { - "position": "vertices" - }, - "file": "grid2d_cyclic_positions" - } + "file": "grid2d_cyclic_positions" + } } diff --git a/applications/procedural/examples/grid2_example.json b/applications/procedural/examples/grid2_example.json index 1e70804ad1..faa66fe75d 100644 --- a/applications/procedural/examples/grid2_example.json +++ b/applications/procedural/examples/grid2_example.json @@ -1 +1,26 @@ -{"edges":320,"faces":200,"input":{"grid":{"coordinates":{"name":"vertices","spacing":[1,1]},"cycles":[false,false,false],"dimensions":[10,10],"tiling":"diagonal"}},"vertices":121} \ No newline at end of file +{ + "edges": 320, + "faces": 200, + "input": { + "grid": { + "coordinates": { + "name": "vertices", + "spacing": [ + 1, + 1 + ] + }, + "cycles": [ + false, + false, + false + ], + "dimensions": [ + 10, + 10 + ], + "tiling": "diagonal" + } + }, + "vertices": 121 +} diff --git a/applications/procedural/examples/grid3_cyclic_example.json b/applications/procedural/examples/grid3_cyclic_example.json index 9067037ff7..2c5d65a3c6 100644 --- a/applications/procedural/examples/grid3_cyclic_example.json +++ b/applications/procedural/examples/grid3_cyclic_example.json @@ -1 +1,29 @@ -{"cells":6000,"edges":7000,"faces":12000,"input":{"grid":{"coordinates":{"name":"","spacing":[0,0,0]},"cycles":[true,true,true],"dimensions":[10,10,10],"tiling":"freudenthal"}},"vertices":1000} \ No newline at end of file +{ + "cells": 6000, + "edges": 7000, + "faces": 12000, + "input": { + "grid": { + "coordinates": { + "name": "", + "spacing": [ + 0, + 0, + 0 + ] + }, + "cycles": [ + true, + true, + true + ], + "dimensions": [ + 10, + 10, + 10 + ], + "tiling": "freudenthal" + } + }, + "vertices": 1000 +} diff --git a/applications/procedural/examples/grid3_cyclic_positions.json b/applications/procedural/examples/grid3_cyclic_positions.json index 6ffa7d892e..e69de29bb2 100644 --- a/applications/procedural/examples/grid3_cyclic_positions.json +++ b/applications/procedural/examples/grid3_cyclic_positions.json @@ -1,24 +0,0 @@ -{ - "grid": { - "cycles": [ - true, - true, - true - ], - "dimensions": [ - 10, - 10, - 10 - ], - "tiling": "freudenthal" - }, - "name": "grid3d_tiled", - "type": "grid" - "output": { - "attributes": { - "position": "vertices" - }, - "file": "grid2d", - "input": "grid2d" - } -} diff --git a/applications/procedural/examples/grid3_example.json b/applications/procedural/examples/grid3_example.json index 5fbe109717..5c7e3136f3 100644 --- a/applications/procedural/examples/grid3_example.json +++ b/applications/procedural/examples/grid3_example.json @@ -1 +1,29 @@ -{"cells":6000,"edges":7930,"faces":12600,"input":{"grid":{"coordinates":{"name":"vertices","spacing":[1,1,1]},"cycles":[false,false,false],"dimensions":[10,10,10],"tiling":"freudenthal"}},"vertices":1331} \ No newline at end of file +{ + "cells": 6000, + "edges": 7930, + "faces": 12600, + "input": { + "grid": { + "coordinates": { + "name": "vertices", + "spacing": [ + 1, + 1, + 1 + ] + }, + "cycles": [ + false, + false, + false + ], + "dimensions": [ + 10, + 10, + 10 + ], + "tiling": "freudenthal" + } + }, + "vertices": 1331 +} diff --git a/applications/procedural/examples/triangle_fan_example.json b/applications/procedural/examples/triangle_fan_example.json index a3fb1e3b78..b846a0e283 100644 --- a/applications/procedural/examples/triangle_fan_example.json +++ b/applications/procedural/examples/triangle_fan_example.json @@ -1 +1,19 @@ -{"edges":41,"faces":20,"input":{"triangle_fan":{"coordinates":{"center":[0,1],"degrees":90,"name":"vertices","radius":3},"size":20}},"vertices":22} \ No newline at end of file +{ + "edges": 41, + "faces": 20, + "input": { + "triangle_fan": { + "coordinates": { + "center": [ + 0, + 1 + ], + "degrees": 90, + "name": "vertices", + "radius": 3 + }, + "size": 20 + } + }, + "vertices": 22 +} diff --git a/applications/procedural/main.cpp b/applications/procedural/main.cpp index af0440c78d..472652031d 100644 --- a/applications/procedural/main.cpp +++ b/applications/procedural/main.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include @@ -17,6 +17,7 @@ #include #include #include +#include "CLI/CLI.hpp" #include "spec.hpp" using namespace wmtk::components; @@ -24,31 +25,21 @@ using namespace wmtk::applications; using namespace wmtk; namespace fs = std::filesystem; -int main(int argc, char* argv[]) -{ - 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); +int run(const fs::path& config_path) +{ nlohmann::json j; { - std::ifstream ifs(json_input_file); + jse::JSE spec_engine; + std::ifstream ifs(config_path); j = nlohmann::json::parse(ifs); - jse::JSE spec_engine; bool r = spec_engine.verify_json(j, applications::procedural::spec); if (!r) { wmtk::logger().error("{}", spec_engine.log2str()); return 1; - } else { - j = spec_engine.inject_defaults(j, applications::procedural::spec); } + j = spec_engine.inject_defaults(j, applications::procedural::spec); } @@ -85,13 +76,14 @@ int main(int argc, char* argv[]) if (!j.contains("output")) { wmtk::logger().info("procedural_app: No output path provided"); } else { - auto opts = j["output"].get(); - if (coordinate_handle_opt.has_value()) { - opts.position_attribute = *coordinate_handle_opt; - wmtk::components::output::output(*mesh, opts); - } + if (coordinate_handle_opt.has_value()) { + auto out_opts = j["output"].get(); + out_opts.position_attribute = *coordinate_handle_opt; + wmtk::components::output::output(*mesh, out_opts); + } } + spdlog::warn("W {}", j.dump(2)); if (j.contains("report")) { const std::string report = j["report"]; if (!report.empty()) { @@ -102,7 +94,110 @@ int main(int argc, char* argv[]) std::ofstream ofs(report); + spdlog::warn("W {}", j.dump(2)); ofs << out_json; } } + return 0; +} + +void fill_config(const fs::path& output_path, const auto& specific_options) +{ + wmtk::logger().info("Filling config for {}", specific_options.name()); + wmtk::components::procedural::ProceduralOptions options; + options.settings = specific_options; + nlohmann::json j; + j.update(options); + + std::ofstream ofs(output_path); + ofs << j; +} + +int main(int argc, char* argv[]) +{ + CLI::App app{argv[0]}; + + app.ignore_case(); + app.require_subcommand(1, 2); + + std::optional json_input_file; + wmtk::components::procedural::DiskOptions disk_options; + wmtk::components::procedural::GridOptions grid_options; + wmtk::components::procedural::TriangleFanOptions triangle_fan_options; + std::optional fill_config_path; + + CLI::App* run_cmd = app.add_subcommand("run", "Run application"); + run_cmd->add_option("-j, --json", json_input_file, "json specification file") + ->required(true) + ->check(CLI::ExistingFile); + + CLI::App* config_cmd = app.add_subcommand("config", "Config creation options"); + config_cmd->add_option("-o, --output", fill_config_path, "Output config")->required(true); + //->check(CLI::ExistingFile); + + CLI::App* grid_cmd = + config_cmd->add_subcommand("grid", "Grid config options")->fallthrough(true); + + + CLI::App* triangle_fan_cmd = + config_cmd->add_subcommand("triangle_fan", "Grid config options")->fallthrough(true); + CLI::App* disk_cmd = config_cmd->add_subcommand("disk", "Disk mesh options")->fallthrough(true); + CLI11_PARSE(app, argc, argv); + + // if (!json_input_file.has_value() && !fill_config_path.has_value()) { + // wmtk::logger().error("An input json file with [-j] is required unless blank config " + // "generation is being used with [--fill-config]"); + // return 1; + // } + + int exit_mode = -1; + + /* + run_cmd->callback([&]() { + spdlog::warn("YOW!"); + assert(json_input_file.has_value()); + exit_mode = run(json_input_file.value()); + }); + + disk_cmd->callback([&]() { + spdlog::warn("YOW!"); + assert(fill_config_path.has_value()); + fill_config(fill_config_path.value(), disk_options); + exit_mode = 0; + }); + triangle_fan_cmd->callback([&]() { + spdlog::warn("YOW!"); + assert(fill_config_path.has_value()); + fill_config(fill_config_path.value(), triangle_fan_options); + exit_mode = 0; + }); + grid_cmd->callback([&]() { + spdlog::warn("YOW!"); + assert(fill_config_path.has_value()); + fill_config(fill_config_path.value(), grid_options); + exit_mode = 0; + }); + + config_cmd->callback([&]() { + spdlog::warn("YOW!"); + assert(fill_config_path.has_value()); + // fill_config(fill_config_path.value(), options); + exit_mode = 0; + }); + + spdlog::info( + "run{} config{} disk{} triangle{} grid{}", + run_cmd->parsed(), + config_cmd->parsed(), + disk_cmd->parsed(), + triangle_fan_cmd->parsed(), + grid_cmd->parsed()); + */ + + + if(run_cmd->parsed()) { + exit_mode = run(json_input_file.value()); + } + assert(exit_mode != -1); // "Some subcommand should have updated the exit mode" + return exit_mode; } 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) ; +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6cedb18913..0c36e8f607 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -8,13 +8,15 @@ 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() # list of all components -add_subdirectory("input") -add_subdirectory("multimesh") +add_subdirectory(input) +add_subdirectory(multimesh) add_subdirectory("output/wmtk/components/output") add_subdirectory("delaunay/wmtk/components/delaunay") add_subdirectory("to_points/wmtk/components/to_points") @@ -26,7 +28,7 @@ add_subdirectory("marching/wmtk/components/marching") add_subdirectory("procedural/wmtk/components/procedural") add_subdirectory("tetwild_simplification/wmtk/components/tetwild_simplification") -add_subdirectory("isotropic_remeshing/wmtk/components/isotropic_remeshing") +add_subdirectory("isotropic_remeshing") add_subdirectory("winding_number/wmtk/components/winding_number") add_subdirectory("wildmeshing/wmtk/components/wildmeshing") @@ -38,11 +40,11 @@ add_subdirectory("CDT/wmtk/components/CDT") add_subdirectory("edge_insertion/wmtk/components/edge_insertion") add_subdirectory("simplicial_embedding/wmtk/components/simplicial_embedding") +add_subdirectory("regular_space/wmtk/components/regular_space") # add_component("export_cache") # add_component("import_cache") # add_component("mesh_info") -# add_component("regular_space") # add_component("tag_intersection") # add_component("wildmeshing") # add_component("get_all_meshes") 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/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() 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/isotropic_remeshing/CMakeLists.txt b/components/isotropic_remeshing/CMakeLists.txt new file mode 100644 index 0000000000..ba4c3baaab --- /dev/null +++ b/components/isotropic_remeshing/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_NAME isotropic_remeshing) +add_subdirectory("src/wmtk/components/isotropic_remeshing") +if(NOT ${WMTK_ENABLE_COMPONENT_${COMPONENT_NAME}}) + return() +endif() +if(WMTK_ENABLE_COMPONENT_TESTS) +add_subdirectory("tests") +endif() diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/CMakeLists.txt b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/CMakeLists.txt new file mode 100644 index 0000000000..54935b726d --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/CMakeLists.txt @@ -0,0 +1,36 @@ +add_component(${COMPONENT_NAME}) +if(NOT ${WMTK_ENABLE_COMPONENT_${COMPONENT_NAME}}) + return() +endif() + +set(SRC_FILES + IsotropicRemeshingOptions.cpp + IsotropicRemeshingOptions.hpp + + IsotropicRemeshing.cpp + IsotropicRemeshing.hpp + + IsotropicRemeshing_split.cpp + IsotropicRemeshing_collapse.cpp + IsotropicRemeshing_smooth.cpp + IsotropicRemeshing_swap.cpp + + isotropic_remeshing.hpp + isotropic_remeshing.cpp + + internal/configure_collapse.hpp + internal/configure_collapse.cpp + + internal/configure_split.hpp + internal/configure_split.cpp + + internal/configure_swap.hpp + internal/configure_swap.cpp + internal/configure_swap_tetmesh.cpp + ) + + +#CURRENT_COMPONENT_LIB_NAME is set from the main cmake +target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) +target_link_libraries(wmtk_${COMPONENT_NAME} PRIVATE nlohmann_json) +target_link_libraries(wmtk_${COMPONENT_NAME} PUBLIC wmtk::output) diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/EdgeSwapMode.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/EdgeSwapMode.hpp new file mode 100644 index 0000000000..1aea0e4bcf --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/EdgeSwapMode.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace wmtk::components::isotropic_remeshing { + enum class EdgeSwapMode { + AMIPS, + Valence, + Skip + }; +} diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.cpp new file mode 100644 index 0000000000..522df1846f --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.cpp @@ -0,0 +1,260 @@ + +#include "IsotropicRemeshing.hpp" + +// main execution tools +#include +#include + + +// +#include +#include +#include + +// utils for setting invariants +#include +#include +#include +#include +// meshvisitor requires knowing all the mesh types +#include +#include +#include +#include + +#include + +// for logging meshes +#include +#include +#include + + +// op types +#include +#include +#include +#include +// +#include + + +namespace wmtk::components::isotropic_remeshing { +IsotropicRemeshing::~IsotropicRemeshing() = default; +IsotropicRemeshing::IsotropicRemeshing(const IsotropicRemeshingOptions& opts) + : m_options(opts) +{ + if (!m_options.position_attribute.is_valid()) { + throw std::runtime_error("Isotropic remeshing run without a valid position attribute"); + } + if (m_options.envelope_size.has_value()) { + make_envelopes(); + } + + make_interior_invariants(); + + + // split + if (m_options.use_split) { + configure_split(); + assert(bool(m_split)); + m_operations.emplace_back("split", m_split); + } else { + wmtk::logger().info("Running Isotropic Remeshing without a split configured"); + } + + + ////////////////////////////////////////// + // collapse + + if (m_options.use_collapse) { + configure_collapse(); + assert(bool(m_collapse)); + m_operations.emplace_back("collapse", m_collapse); + } else { + wmtk::logger().info("Running Isotropic Remeshing without a collapse configured"); + } + + + ////////////////////////////////////////// + // swap + + if (m_options.use_swap) { + configure_swap(); + assert(bool(m_swap)); + m_operations.emplace_back("swap", m_swap); + } else if (m_options.edge_swap_mode != EdgeSwapMode::Skip) { + wmtk::logger().info("Running Isotropic Remeshing without a swap configured despite being " + "supposed to use them"); + } + + ////////////////////////////////////////// + // smooth + if (m_options.use_smooth) { + configure_smooth(); + assert(bool(m_smooth)); + m_operations.emplace_back("smooth", m_smooth); + } else { + wmtk::logger().info("Running Isotropic Remeshing without a smooth configured"); + } +} + +std::vector IsotropicRemeshing::all_envelope_positions() const +{ + std::vector handles; + if (m_options.envelope_size.has_value()) { + auto try_add = [&](const wmtk::attribute::MeshAttributeHandle& h) { + if (is_envelope_position(h)) { + handles.emplace_back(h); + } + }; + + for (const auto& h : m_options.all_positions()) { + try_add(h); + } + } + return handles; +} + +bool IsotropicRemeshing::is_envelope_position(const wmtk::attribute::MeshAttributeHandle& position) +{ + return position.mesh().top_cell_dimension() < position.dimension(); +} + + +void IsotropicRemeshing::make_interior_invariants() +{ + auto position = m_options.position_attribute; + Mesh& mesh = position.mesh(); + auto invariant_interior_vertex = std::make_shared(mesh); + m_interior_edge_invariants = std::make_shared(mesh); + + auto set_all_invariants = [&](auto&& m) { + // TODO: this used to do vertex+edge, but just checkign for vertex should be sufficient? + for (PrimitiveType pt = PrimitiveType::Vertex; pt < m.top_simplex_type(); pt = pt + 1) { + invariant_interior_vertex->add( + std::make_shared(m, pt)); + } + + m_interior_edge_invariants->add( + std::make_shared(m, PrimitiveType::Edge)); + }; + wmtk::multimesh::MultiMeshVisitor visitor(set_all_invariants); + visitor.execute_from_root(mesh); + m_interior_position_invariants = invariant_interior_vertex; +} + +void IsotropicRemeshing::run() +{ + using namespace internal; + + + // TODO: brig me back! + // mesh_in->clear_attributes(keeps); + + // gather handles again as they were invalidated by clear_attributes + // positions = utils::get_attributes(cache, *mesh_in, + // m_options.position_attribute); assert(positions.size() == 1); position = + // positions.front(); pass_through_attributes = utils::get_attributes(cache, + // *mesh_in, m_options.pass_through_attributes); + + + // assert(dynamic_cast(&position.mesh()) != nullptr); + + // TriMesh& mesh = static_cast(position.mesh()); + + + auto position = m_options.position_attribute; + Mesh& mesh = position.mesh(); + + + auto log_mesh = [&](int64_t index) { + if (m_options.intermediate_output_format.empty() && m_options.mesh_collection == nullptr) { + wmtk::logger().error("Failed to log using intermediate_output_format because " + "no MeshCollection was attached"); + return; + } + for (const auto& [name, opts] : m_options.intermediate_output_format) { + auto opt = wmtk::components::output::utils::format(opts, index); + wmtk::components::output::output(m_options.mesh_collection->get_mesh(name), opt); + } + }; + + log_mesh(0); + + + ////////////////////////////////////////// + Scheduler scheduler; + for (long i = 1; i <= m_options.iterations; ++i) { + wmtk::logger().info("Iteration {}", i); + + SchedulerStats pass_stats; + for (const auto& [name, opptr] : m_operations) { + if (!bool(opptr)) { + spdlog::warn("op {} is empty", name); + continue; + } + const auto stats = scheduler.run_operation_on_all(*opptr); + logger().info( + "Executed {} {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, executing: {}", + stats.number_of_performed_operations(), + name, + stats.number_of_successful_operations(), + stats.number_of_failed_operations(), + stats.collecting_time, + stats.sorting_time, + stats.executing_time); + pass_stats += stats; + } + + wmtk::multimesh::consolidate(mesh); + + 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); + + wmtk::multimesh::consolidate(mesh); + log_mesh(i); + } +} + +void IsotropicRemeshing::make_envelope_invariants() +{ + make_envelopes(); +} + +void IsotropicRemeshing::make_envelopes() +{ + if (!m_options.envelope_size.has_value()) { + throw std::runtime_error( + "Could not create envelopes because options do not have an envelope size"); + } + auto envelope_positions = all_envelope_positions(); + + std::vector> envelope_invariants; + + + std::transform( + envelope_positions.begin(), + envelope_positions.end(), + std::back_inserter(envelope_invariants), + [&](const wmtk::attribute::MeshAttributeHandle& mah) { + return std::make_shared( + mah, + std::sqrt(2) * m_options.envelope_size.value(), + mah); + }); + + m_envelope_invariants = std::make_shared( + m_options.position_attribute.mesh().get_multi_mesh_root()); + + for (const auto& invar : envelope_invariants) { + m_envelope_invariants->add(invar); + } +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.hpp new file mode 100644 index 0000000000..814da50243 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "IsotropicRemeshingOptions.hpp" + + +namespace wmtk { +namespace operations { +class EdgeCollapse; +class EdgeSplit; +namespace composite { +class EdgeSwap; +} +class Operation; +class AttributesUpdateWithFunction; +} // namespace operations +namespace invariants { +class EnvelopeInvariant; +class InteriorSimplexInvariant; +class InvariantCollection; +} // namespace invariants +} // namespace wmtk +namespace wmtk::components::isotropic_remeshing { + +class IsotropicRemeshing +{ +public: + IsotropicRemeshing(const IsotropicRemeshingOptions& opts); + ~IsotropicRemeshing(); + + + void run(); + +private: + std::vector all_envelope_positions() const; + static bool is_envelope_position(const wmtk::attribute::MeshAttributeHandle& position); + void make_envelopes(); + void make_envelope_invariants(); + void make_interior_invariants(); + +private: + IsotropicRemeshingOptions m_options; + + + std::shared_ptr m_split; + std::shared_ptr m_collapse; + std::shared_ptr m_swap; + std::shared_ptr m_smooth; + + std::vector>> m_operations; + // std::vector> m_envelope_invariants; + std::shared_ptr m_envelope_invariants; + + std::shared_ptr m_interior_position_invariants; + std::shared_ptr m_interior_edge_invariants; + + void configure_split(); + void configure_collapse(); + void configure_swap(); + void configure_smooth(); +}; + +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp new file mode 100644 index 0000000000..a6d3da7136 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp @@ -0,0 +1,143 @@ +#include "IsotropicRemeshingOptions.hpp" +#include +#include +#include +#include + +#include + +#define DEFAULT_PARSABLE_ARGS \ + iterations, lock_boundary, use_for_periodic, fix_uv_seam, intermediate_output_format, \ + use_split, use_swap, use_collapse, use_smooth + +namespace wmtk::components::isotropic_remeshing { +namespace { + +// compute the length relative to the bounding box diagonal +double relative_to_absolute_length( + const attribute::MeshAttributeHandle& position, + const double length_rel) +{ + if (!position.is_valid()) { + throw std::runtime_error( + "Could not convert relative to absolute length because position attr was invalid"); + } + auto pos = position.mesh().create_const_accessor(position); + const auto vertices = position.mesh().get_all(PrimitiveType::Vertex); + Eigen::AlignedBox bbox(pos.dimension()); + + + for (const auto& v : vertices) { + bbox.extend(pos.const_vector_attribute(v)); + } + + const double diag_length = bbox.sizes().norm(); + wmtk::logger().debug( + "computed absolute target length using relative factor {} on bbox diagonal {}", + length_rel, + diag_length); + + return length_rel * diag_length; +} +} // namespace + +double IsotropicRemeshingOptions::get_absolute_length() const +{ + double length = length_abs; + if (length_abs <= 0) { + if (length_rel <= 0) { + throw std::runtime_error("Either absolute or relative length must be set!"); + } + length = relative_to_absolute_length(position_attribute, length_rel); + } else { + wmtk::logger().debug( + "get_absolute_length using absolute length value {} {}", + length, + length_abs); + } + return length; +} +void to_json(nlohmann::json& nlohmann_json_j, const IsotropicRemeshingOptions& nlohmann_json_t) +{ + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, DEFAULT_PARSABLE_ARGS)); + + if (nlohmann_json_t.length_abs != 0) { + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, length_abs)); + } else { + assert(nlohmann_json_t.length_rel != 0); + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, length_rel)); + } + if (nlohmann_json_t.envelope_size.has_value()) { + nlohmann_json_j["envelope_size"] = nlohmann_json_t.envelope_size.value(); + } + { + std::string name; + switch (nlohmann_json_t.edge_swap_mode) { + case EdgeSwapMode::AMIPS: name = "amips"; break; + case EdgeSwapMode::Valence: name = "valence"; break; + default: + case EdgeSwapMode::Skip: name = "skip"; break; + } + nlohmann_json_j["edge_swap_mode"] = name; + } + + if (!nlohmann_json_t.intermediate_output_format.empty()) { + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, intermediate_output_format)); + } +} +void from_json(const nlohmann::json& nlohmann_json_j, IsotropicRemeshingOptions& nlohmann_json_t) +{ + WMTK_NLOHMANN_JSON_DECLARE_DEFAULT_OBJECT(IsotropicRemeshingOptions); + WMTK_NLOHMANN_ASSIGN_TYPE_FROM_JSON_WITH_DEFAULT(DEFAULT_PARSABLE_ARGS); + + if (nlohmann_json_j.contains("length_abs")) { + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, length_abs)); + wmtk::logger().debug("Got an absolute length {}", nlohmann_json_t.length_abs); + } else { + assert(nlohmann_json_j.contains("length_rel")); + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, length_rel)); + wmtk::logger().debug("Got a relative length {}", nlohmann_json_t.length_rel); + } + + if (nlohmann_json_j.contains("envelope_size")) { + nlohmann_json_t.envelope_size = nlohmann_json_j["envelope_size"].get(); + } + + assert(nlohmann_json_j.contains("edge_swap_mode")); + const std::string swap_name = nlohmann_json_j["edge_swap_mode"].get(); + if (swap_name == "amips") { + nlohmann_json_t.edge_swap_mode = EdgeSwapMode::AMIPS; + } else if (swap_name == "valence") { + nlohmann_json_t.edge_swap_mode = EdgeSwapMode::Valence; + } else if (swap_name == "skip") { + nlohmann_json_t.edge_swap_mode = EdgeSwapMode::Skip; + } else { + throw std::runtime_error(fmt::format( + "Expected edge_swap_mode to be one of [amips,valence,skip], got [{}]", + swap_name)); + } +} +void IsotropicRemeshingOptions::load_json(const nlohmann::json& js) +{ + from_json(js, *this); +} +void IsotropicRemeshingOptions::write_json(nlohmann::json& js) const +{ + to_json(js, *this); +} + +std::vector IsotropicRemeshingOptions::all_positions() const +{ + std::vector r = other_position_attributes; + r.emplace_back(position_attribute); + if (inversion_position_attribute.has_value()) { + r.emplace_back(inversion_position_attribute.value()); + } + std::sort(r.begin(), r.end()); + + r.erase(std::unique(r.begin(), r.end()), r.end()); + + return r; +} + +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp new file mode 100644 index 0000000000..e30fa2270e --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include +#include +#include "EdgeSwapMode.hpp" + + +namespace wmtk::components::multimesh { +class MeshCollection; +} +namespace wmtk::components::output { +class MeshCollection; +} + +namespace wmtk::components::isotropic_remeshing { + + +struct IsotropicRemeshingOptions +{ + // std::shared_ptr input; + wmtk::attribute::MeshAttributeHandle position_attribute; + std::optional inversion_position_attribute; + std::vector other_position_attributes; + std::optional sizing_field_attribute; + std::optional visited_edge_flag; + std::optional target_edge_length; + + std::vector pass_through_attributes; + int64_t iterations = 10; + double length_abs = 0; + double length_rel = 0; + bool lock_boundary = true; + bool use_for_periodic = false; + bool fix_uv_seam = false; + + bool use_split = true; + bool use_swap = true; + bool use_collapse = true; + bool use_smooth = true; + // this should be true for periodic + bool separate_substructures = false; + + EdgeSwapMode edge_swap_mode = EdgeSwapMode::Skip; + + std::optional envelope_size; // 1e-3 + + + void load_json(const nlohmann::json& js); + void write_json(nlohmann::json& js) const; + + + // transforms the absoltue or relative length paramters into an absolute length parameter + double get_absolute_length() const; + + + friend void to_json( + nlohmann::json& nlohmann_json_j, + const IsotropicRemeshingOptions& nlohmann_json_t); + friend void from_json( + const nlohmann::json& nlohmann_json_j, + IsotropicRemeshingOptions& nlohmann_json_t); + + std::vector all_positions() const; + + + wmtk::components::multimesh::MeshCollection* mesh_collection; + // format for outputting intermediate results. Assumed to just be a frame number, i.e something + // like format("path_{}.hdf5",0) to generate path_0.hdf5 + std::map intermediate_output_format; +}; + + +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_collapse.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_collapse.cpp new file mode 100644 index 0000000000..29c626a465 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_collapse.cpp @@ -0,0 +1,32 @@ + +#include +#include +#include +#include +#include +#include +#include +#include "IsotropicRemeshing.hpp" + + +#include +#include + +#include +#include "internal/configure_collapse.hpp" + +namespace wmtk::components::isotropic_remeshing { +void IsotropicRemeshing::configure_collapse() +{ + wmtk::logger().debug("Configure isotropic remeshing collapse"); + wmtk::Mesh& mesh = m_options.position_attribute.mesh(); + auto op = std::make_shared(mesh); + internal::configure_collapse(*op, mesh, m_options); + + if (m_envelope_invariants) { + op->add_invariant(m_envelope_invariants); + } + assert(op->attribute_new_all_configured()); + m_collapse = op; +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_smooth.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_smooth.cpp new file mode 100644 index 0000000000..fa8e5a51b3 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_smooth.cpp @@ -0,0 +1,98 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "IsotropicRemeshing.hpp" + + +namespace wmtk::components::isotropic_remeshing { +void IsotropicRemeshing::configure_smooth() +{ + auto position = m_options.position_attribute; + Mesh& mesh = position.mesh(); + auto pass_through_attributes = m_options.pass_through_attributes; + auto other_positions = m_options.other_position_attributes; + assert(mesh.is_connectivity_valid()); + + std::vector positions = other_positions; + positions.push_back(position); + // if (position.mesh().top_simplex_type() != PrimitiveType::Triangle) { + // log_and_throw_error( + // "isotropic remeshing works only for triangle meshes: {}", + // primitive_type_name(position.mesh().top_simplex_type())); + // } + + + // clear attributes + std::vector keeps = pass_through_attributes; + keeps.emplace_back(position); + keeps.insert(keeps.end(), other_positions.begin(), other_positions.end()); + + auto op_smooth = std::make_shared(mesh); + + std::shared_ptr proj_op; + + if (position.dimension() == 3 && mesh.top_simplex_type() == PrimitiveType::Triangle) { + proj_op = std::make_shared(op_smooth); + proj_op->add_constraint(position, position); + m_smooth = proj_op; + } else { + m_smooth = op_smooth; + } + auto update_position_func = [](const Eigen::MatrixXd& P) -> Eigen::VectorXd { + return P.col(0); + }; + std::shared_ptr> + update_position; + std::optional position_for_inversion = + m_options.inversion_position_attribute; + + if (!m_options.other_position_attributes.empty() && !m_options.fix_uv_seam) { + update_position = + std::make_shared>( + other_positions.front(), + position, + update_position_func); + } + + if (position.dimension() == 3 && mesh.top_simplex_type() == PrimitiveType::Triangle) { + op_smooth->set_function(operations::VertexTangentialLaplacianSmooth(position)); + } else { + op_smooth->set_function(operations::VertexLaplacianSmooth(position)); + } + + if (m_options.lock_boundary) { + if (!bool(m_interior_position_invariants)) { + throw std::runtime_error("Interior position invariants were not set despite boundary " + "locking being requested"); + } + op_smooth->add_invariant(m_interior_position_invariants); + } + + // hack for uv + if (m_options.fix_uv_seam) { + proj_op->add_invariant( + std::make_shared(mesh, other_positions.front().mesh())); + } + + if (position_for_inversion) { + proj_op->add_invariant(std::make_shared>( + position_for_inversion.value().mesh(), + position_for_inversion.value().as())); + } + if (m_envelope_invariants) { + op_smooth->add_invariant(m_envelope_invariants); + } + + if (update_position) proj_op->add_transfer_strategy(update_position); +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_split.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_split.cpp new file mode 100644 index 0000000000..32df433381 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_split.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include +#include "IsotropicRemeshing.hpp" + + +#include +#include + +#include +#include "internal/configure_collapse.hpp" +#include "internal/configure_split.hpp" +#include "internal/configure_swap.hpp" + +namespace wmtk::components::isotropic_remeshing { +void IsotropicRemeshing::configure_split() +{ + wmtk::logger().debug("Configure isotropic remeshing split"); + wmtk::Mesh& mesh = m_options.position_attribute.mesh(); + auto op = std::make_shared(mesh); + internal::configure_split(*op, mesh, m_options); + + if (m_options.lock_boundary && !m_options.use_for_periodic) { + op->add_invariant(m_interior_position_invariants); + } + + assert(op->attribute_new_all_configured()); + m_split = op; +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_swap.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_swap.cpp new file mode 100644 index 0000000000..e8fe7b1207 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/IsotropicRemeshing_swap.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "IsotropicRemeshing.hpp" +#include "internal/configure_collapse.hpp" +#include "internal/configure_split.hpp" +#include "internal/configure_swap.hpp" +namespace wmtk::components::isotropic_remeshing { + +void IsotropicRemeshing::configure_swap() +{ + // adds common invariants like inversion check and asserts taht the swap is ready for prime time + wmtk::logger().debug("Configure isotropic remeshing swap"); + if (m_options.edge_swap_mode == EdgeSwapMode::Skip) { + return; + } + wmtk::Mesh& mesh = m_options.position_attribute.mesh(); + switch (mesh.top_simplex_type()) { + case PrimitiveType::Triangle: + m_swap = std::make_shared(static_cast(mesh)); + break; + case PrimitiveType::Tetrahedron: + m_swap = std::make_shared(static_cast(mesh)); + break; + case PrimitiveType::Vertex: + case PrimitiveType::Edge: + default: assert(false); break; + } + + + const std::optional& position_for_inversion = + m_options.inversion_position_attribute; + + switch (m_options.edge_swap_mode) { + case EdgeSwapMode::Valence: { + auto tri = dynamic_cast(&mesh); + if (tri == nullptr) { + throw std::runtime_error( + "EdgeSwap only works with trimesh, got a different type of mesh"); + } + auto invariant_valence_improve = + std::make_shared(*tri); + m_swap->add_invariant(invariant_valence_improve); + break; + } + case EdgeSwapMode::AMIPS: { + } + default: + case EdgeSwapMode::Skip: { + assert(false); + } + } + // if (position_for_inversion) { + // m_swap->collapse().add_invariant(std::make_shared>( + // position_for_inversion.value().mesh(), + // position_for_inversion.value().as())); + // } + // m_swap->add_invariant(m_interior_edge_invariants); + + // internal::configure_split(m_swap->split(), mesh, m_options); + // internal::configure_collapse(m_swap->collapse(), mesh, m_options); + m_swap->collapse().add_invariant(internal::collapse_core_invariants(mesh, m_options)); + + + if (m_envelope_invariants) { + m_swap->add_invariant(m_envelope_invariants); + } + + + for (const auto& p : m_options.all_positions()) { + internal::configure_swap_transfer(*m_swap, p); + } + // clear out all the pass through attributes + for (const auto& attr : m_options.pass_through_attributes) { + m_swap->split().set_new_attribute_strategy(attr); + m_swap->collapse().set_new_attribute_strategy(attr); + } + internal::finalize_swap(*m_swap, m_options); + // m_swap = op; +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.cpp new file mode 100644 index 0000000000..bc2969223f --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.cpp @@ -0,0 +1,118 @@ +#include "configure_collapse.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../IsotropicRemeshingOptions.hpp" +namespace wmtk::components::isotropic_remeshing::internal { + +std::shared_ptr collapse_core_invariants( + Mesh& m, + const IsotropicRemeshingOptions& options) +{ + auto& root = m.get_multi_mesh_root(); + auto ic_root = std::make_shared(root); + auto invariant_link_condition = + std::make_shared(root); + + auto invariant_mm_map = std::make_shared(root); + ic_root->add(invariant_link_condition); + ic_root->add(invariant_mm_map); + if (options.separate_substructures) { + auto invariant_separate_substructures = + std::make_shared(root); + ic_root->add(invariant_separate_substructures); + } + return ic_root; +} + +std::shared_ptr collapse_invariants( + Mesh& m, + const IsotropicRemeshingOptions& options) +{ + auto ic_root = collapse_core_invariants(m, options); + + auto ic = std::make_shared(m); + const std::optional& position_for_inversion = + options.inversion_position_attribute; + + + if (position_for_inversion) { + ic->add(std::make_shared>( + position_for_inversion.value().mesh(), + position_for_inversion.value().as())); + } + + ic->add(ic_root); + const double length_min = (4. / 5.) * options.get_absolute_length(); + auto invariant_max_edge_length = std::make_shared( + options.position_attribute.mesh(), + options.position_attribute.as(), + length_min * length_min); + ic->add(invariant_max_edge_length); + + /* + + // hack for uv + if (options.fix_uv_seam) { + assert( + options.other_position_attributes.size() == + 1); // this only works with a 2d pos + uv mesh for now + + ic->add(std::make_shared( + m, + options.other_position_attributes.front().mesh())); + } + + + if (options.lock_boundary && !options.use_for_periodic) { + auto invariant_interior_edge = std::make_shared(m); + ic->add(invariant_interior_edge); + } + */ + return ic; +} + +void configure_collapse( + operations::EdgeCollapse& ec, + Mesh& m, + const IsotropicRemeshingOptions& options) +{ + auto invars = collapse_invariants(m, options); + ec.add_invariant(invars); + + + const auto positions = options.all_positions(); + + if (options.lock_boundary && !options.use_for_periodic) { + // set collapse towards boundary + for (auto& p : positions) { + auto tmp = std::make_shared>(p); + tmp->set_strategy(operations::CollapseBasicStrategy::Mean); + tmp->set_simplex_predicate(operations::BasicSimplexPredicate::IsInterior); + ec.set_new_attribute_strategy(p, tmp); + } + } else if (options.use_for_periodic) { + assert(false); // TODO: make fusion simplex invariant + ec.add_invariant( + std::make_shared(m, m.get_multi_mesh_root())); + for (auto& p : positions) { + ec.set_new_attribute_strategy(p, operations::CollapseBasicStrategy::Mean); + } + } else { + for (auto& p : positions) { + ec.set_new_attribute_strategy(p, operations::CollapseBasicStrategy::Mean); + } + } + + for (const auto& attr : options.pass_through_attributes) { + ec.set_new_attribute_strategy(attr); + } +} +} // namespace wmtk::components::isotropic_remeshing::internal diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.hpp new file mode 100644 index 0000000000..cf8fc86356 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_collapse.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +namespace wmtk { +namespace invariants { +class InvariantCollection; +} + +namespace operations { +class EdgeCollapse; +} +class Mesh; +} // namespace wmtk + +namespace wmtk::components::isotropic_remeshing { + +struct IsotropicRemeshingOptions; + +namespace internal { + +std::shared_ptr collapse_core_invariants( + Mesh& m, + const IsotropicRemeshingOptions&); + +std::shared_ptr collapse_invariants( + Mesh& m, + const IsotropicRemeshingOptions&); +void configure_collapse(operations::EdgeCollapse& ec, Mesh& m, const IsotropicRemeshingOptions&); +} // namespace internal +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.cpp new file mode 100644 index 0000000000..34fba5130f --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.cpp @@ -0,0 +1,43 @@ +#include "configure_split.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "../IsotropicRemeshingOptions.hpp" + +namespace wmtk::components::isotropic_remeshing::internal { +std::shared_ptr split_invariants( + Mesh& m, + const IsotropicRemeshingOptions& options) +{ + auto ic = std::make_shared(m); + const double length_max = (4. / 3.) * options.get_absolute_length(); + auto invariant_min_edge_length = std::make_shared( + options.position_attribute.mesh(), + options.position_attribute.as(), + length_max * length_max); + ic->add(invariant_min_edge_length); + return ic; +} + +void configure_split(operations::EdgeSplit& es, Mesh& m, const IsotropicRemeshingOptions& options) +{ + es.attribute_new_all_configured(); + auto invars = split_invariants(m, options); + es.add_invariant(invars); + for (auto& p : options.all_positions()) { + es.set_new_attribute_strategy( + p, + operations::SplitBasicStrategy::None, + operations::SplitRibBasicStrategy::Mean); + } + for (const auto& attr : options.pass_through_attributes) { + es.set_new_attribute_strategy(attr); + } + assert(es.attribute_new_all_configured()); +} + +} // namespace wmtk::components::isotropic_remeshing::internal diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.hpp new file mode 100644 index 0000000000..6d0ec70221 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_split.hpp @@ -0,0 +1,28 @@ + +#pragma once +#include + +namespace wmtk { +namespace invariants { +class InvariantCollection; +} + +namespace operations { +class EdgeSplit; +} +class Mesh; +} // namespace wmtk + +namespace wmtk::components::isotropic_remeshing { + +struct IsotropicRemeshingOptions; + +namespace internal { + + +std::shared_ptr split_invariants( + Mesh& m, + const IsotropicRemeshingOptions&); +void configure_split(operations::EdgeSplit& ec, Mesh& m, const IsotropicRemeshingOptions&); +} // namespace internal +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.cpp new file mode 100644 index 0000000000..66f938cc3a --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.cpp @@ -0,0 +1,93 @@ +#include "configure_swap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../IsotropicRemeshingOptions.hpp" +#include "configure_collapse.hpp" + +namespace wmtk::components::isotropic_remeshing::internal { +std::shared_ptr tet_swap( + TetMesh& mesh, + const IsotropicRemeshingOptions& options); + + +void finalize_swap(operations::composite::EdgeSwap& op, const IsotropicRemeshingOptions& options) +{ + assert(op.split().attribute_new_all_configured()); + assert(op.collapse().attribute_new_all_configured()); +}; + +namespace { +std::shared_ptr tri_swap( + TriMesh& mesh, + const IsotropicRemeshingOptions& options) +{ + auto swap = std::make_shared(mesh); + // hack for uv + if (options.fix_uv_seam) { + swap->add_invariant(std::make_shared( + mesh, + options.other_position_attributes.front().mesh())); + } + auto collapse_invars = collapse_invariants(mesh, options); + + switch (options.edge_swap_mode) { + case EdgeSwapMode::Valence: { + auto invariant_valence_improve = + std::make_shared(mesh); + swap->add_invariant(invariant_valence_improve); + break; + } + case EdgeSwapMode::AMIPS: { + } + default: + case EdgeSwapMode::Skip: { + assert(false); + } + } + swap->collapse().add_invariant(collapse_invars); + + for (const auto& p : options.all_positions()) { + swap->split().set_new_attribute_strategy( + p, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::Mean); + swap->collapse().set_new_attribute_strategy( + p, + wmtk::operations::CollapseBasicStrategy::CopyOther); + } + for (const auto& attr : options.pass_through_attributes) { + swap->split().set_new_attribute_strategy(attr); + swap->collapse().set_new_attribute_strategy(attr); + } + finalize_swap(*swap, options); + return swap; +} + +} // namespace + + +void configure_swap_transfer( + operations::composite::EdgeSwap& swap, + const attribute::MeshAttributeHandle& vertex_handle) +{ + swap.split().set_new_attribute_strategy(vertex_handle); + + swap.split().set_new_attribute_strategy( + vertex_handle, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::Mean); + swap.collapse().set_new_attribute_strategy( + vertex_handle, + wmtk::operations::CollapseBasicStrategy::CopyOther); +} + +} // namespace wmtk::components::isotropic_remeshing::internal diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.hpp new file mode 100644 index 0000000000..0bd6cb16ae --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +namespace wmtk { +class Mesh; +namespace attribute { +class MeshAttributeHandle; +} +namespace operations { +class Operation; +namespace composite { +class EdgeSwap; +} +} // namespace operations +} // namespace wmtk +namespace wmtk::components::isotropic_remeshing { + +struct IsotropicRemeshingOptions; + +namespace internal { +void finalize_swap(operations::composite::EdgeSwap& op, const IsotropicRemeshingOptions& options); +std::shared_ptr configure_swap( + Mesh& m, + const IsotropicRemeshingOptions& opts); + +void configure_swap_transfer( + operations::composite::EdgeSwap& swap, + const attribute::MeshAttributeHandle& vertex_handle); +} // namespace internal +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap_tetmesh.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap_tetmesh.cpp new file mode 100644 index 0000000000..993dfee0c3 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/configure_swap_tetmesh.cpp @@ -0,0 +1,359 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../IsotropicRemeshingOptions.hpp" +#include "configure_collapse.hpp" +#include "configure_split.hpp" +#include "configure_swap.hpp" +#include +namespace wmtk::components::isotropic_remeshing::internal { + +namespace { + +auto compute_tet_amips = [](const Eigen::MatrixXd& P) -> Eigen::VectorXd { + // tet + 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); + } + } + const double a = wmtk::function::utils::Tet_AMIPS_energy(pts); + return Eigen::VectorXd::Constant(1, a); +}; +auto get_tet_amips_transfer_strategy( + const wmtk::attribute::MeshAttributeHandle& position_handle, + const wmtk::attribute::MeshAttributeHandle& amips_handle) +{ + return std::make_shared>( + amips_handle, + position_handle, + compute_tet_amips); +} + +auto initiate_amips_transfer_attribute( + wmtk::Mesh& mesh, + const wmtk::attribute::MeshAttributeHandle& position_handle, + const std::string& amips_attribute_name = "amips", + bool initiate_values = true) +{ + auto amips_handle = + mesh.register_attribute(amips_attribute_name, PrimitiveType::Tetrahedron, 1); + + if (initiate_values) { + auto amips_update = get_tet_amips_transfer_strategy(position_handle, amips_handle); + amips_update->run_on_all(); + } + return amips_handle; +} + + +void configure_core_tet_swap( + operations::composite::TetEdgeSwap& swap, + const IsotropicRemeshingOptions& options) +{ + swap.collapse().add_invariant(collapse_core_invariants(swap.mesh(), options)); + + for (const auto& pos_attr : options.all_positions()) { + swap.collapse().set_new_attribute_strategy( + pos_attr, + operations::CollapseBasicStrategy::CopyOther); + swap.split().set_new_attribute_strategy(pos_attr); + } + if (const auto& sfa_opt = options.sizing_field_attribute; sfa_opt.has_value()) { + swap.collapse().set_new_attribute_strategy( + sfa_opt.value(), + operations::CollapseBasicStrategy::CopyOther); + swap.split().set_new_attribute_strategy(sfa_opt.value()); + } + // swap.split().add_transfer_strategy(amips_update); + // swap.collapse().add_transfer_strategy(amips_update); + if (const auto& flag_h = options.visited_edge_flag; flag_h.has_value()) { + swap.split().set_new_attribute_strategy( + flag_h.value(), + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + swap.collapse().set_new_attribute_strategy( + flag_h.value(), + wmtk::operations::CollapseBasicStrategy::None); + } + + if (const auto& edge_length_h = options.target_edge_length; edge_length_h.has_value()) { + swap.split().set_new_attribute_strategy( + edge_length_h.value(), + wmtk::operations::SplitBasicStrategy::Copy, + wmtk::operations::SplitRibBasicStrategy::Mean); + swap.collapse().set_new_attribute_strategy( + edge_length_h.value(), + wmtk::operations::CollapseBasicStrategy::None); + } + for (const auto& attr : options.pass_through_attributes) { + swap.split().set_new_attribute_strategy( + attr, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + swap.collapse().set_new_attribute_strategy( + attr, + wmtk::operations::CollapseBasicStrategy::None); + } +} + +} // namespace +std::shared_ptr +tet_swap56(TetMesh& mesh, const IsotropicRemeshingOptions& options) +{ + TetMesh& tetmesh = static_cast(mesh); + auto swap56 = std::make_shared(mesh); + for (int i = 0; i < 5; ++i) { + auto swap = std::make_shared(mesh, i); + + configure_core_tet_swap(*swap, options); + + assert(options.position_attribute.holds()); + swap->add_invariant(std::make_shared( + mesh, + options.position_attribute.as(), + i)); + + auto amips_handle = initiate_amips_transfer_attribute(mesh, options.position_attribute); + swap->add_transfer_strategy( + get_tet_amips_transfer_strategy(options.position_attribute, amips_handle)); + + + // swap->collapse().add_invariant(envelope_invariant); + + + finalize_swap(*swap, options); + swap56->add_operation(swap); + } + return std::static_pointer_cast(swap56); +} +// +// +std::shared_ptr +tet_swap44(TetMesh& mesh, const IsotropicRemeshingOptions& options) +{ + // swap44 + + auto swap44 = std::make_shared(mesh); + for (int i = 0; i < 2; ++i) { + auto swap = std::make_shared(mesh, i); + + configure_core_tet_swap(*swap, options); + + swap->add_invariant(std::make_shared( + mesh, + options.position_attribute.as(), + i)); + + + // swap->collapse().add_invariant(envelope_invariant); + + + swap44->add_operation(swap); + } + + auto swap44_energy_check = [&](int64_t idx, const simplex::Simplex& t) -> double { + constexpr static PrimitiveType PV = PrimitiveType::Vertex; + constexpr static PrimitiveType PE = PrimitiveType::Edge; + constexpr static PrimitiveType PF = PrimitiveType::Triangle; + constexpr static PrimitiveType PT = PrimitiveType::Tetrahedron; + + assert(options.position_attribute.holds()); + auto accessor = mesh.create_const_accessor(options.position_attribute.as()); + + // get the coords of the vertices + // input edge end points + const Tuple e0 = t.tuple(); + const Tuple e1 = mesh.switch_tuple(e0, PV); + // other four vertices + std::array v; + auto iter_tuple = e0; + for (int64_t i = 0; i < 4; ++i) { + v[i] = mesh.switch_tuples(iter_tuple, {PE, PV}); + iter_tuple = mesh.switch_tuples(iter_tuple, {PF, PT}); + } + + if (iter_tuple != e0) return 0; + assert(iter_tuple == e0); + + std::array, 6> positions = { + {accessor.const_vector_attribute(v[(idx + 0) % 4]), + accessor.const_vector_attribute(v[(idx + 1) % 4]), + accessor.const_vector_attribute(v[(idx + 2) % 4]), + accessor.const_vector_attribute(v[(idx + 3) % 4]), + accessor.const_vector_attribute(e0), + accessor.const_vector_attribute(e1)}}; + std::array positions_double = { + {positions[0].cast(), + positions[1].cast(), + positions[2].cast(), + positions[3].cast(), + positions[4].cast(), + positions[5].cast()}}; + + std::array, 4> new_tets = { + {{{0, 1, 2, 4}}, {{0, 2, 3, 4}}, {{0, 1, 2, 5}}, {{0, 2, 3, 5}}}}; + + double new_energy_max = std::numeric_limits::lowest(); + + for (int i = 0; i < 4; ++i) { + if (wmtk::utils::wmtk_orient3d( + positions[new_tets[i][0]], + positions[new_tets[i][1]], + positions[new_tets[i][2]], + positions[new_tets[i][3]]) > 0) { + auto energy = wmtk::function::utils::Tet_AMIPS_energy({{ + positions_double[new_tets[i][0]][0], + positions_double[new_tets[i][0]][1], + positions_double[new_tets[i][0]][2], + positions_double[new_tets[i][1]][0], + positions_double[new_tets[i][1]][1], + positions_double[new_tets[i][1]][2], + positions_double[new_tets[i][2]][0], + positions_double[new_tets[i][2]][1], + positions_double[new_tets[i][2]][2], + positions_double[new_tets[i][3]][0], + positions_double[new_tets[i][3]][1], + positions_double[new_tets[i][3]][2], + }}); + + if (energy > new_energy_max) new_energy_max = energy; + } else { + auto energy = wmtk::function::utils::Tet_AMIPS_energy({{ + positions_double[new_tets[i][1]][0], + positions_double[new_tets[i][1]][1], + positions_double[new_tets[i][1]][2], + positions_double[new_tets[i][0]][0], + positions_double[new_tets[i][0]][1], + positions_double[new_tets[i][0]][2], + positions_double[new_tets[i][2]][0], + positions_double[new_tets[i][2]][1], + positions_double[new_tets[i][2]][2], + positions_double[new_tets[i][3]][0], + positions_double[new_tets[i][3]][1], + positions_double[new_tets[i][3]][2], + }}); + + if (energy > new_energy_max) new_energy_max = energy; + } + } + + return new_energy_max; + }; + + swap44->set_value_function(swap44_energy_check); + swap44->add_invariant(std::make_shared(mesh, 4)); + return swap44; +} +// std::shared_ptr +// tet_swap44(TetMesh& mesh, const IsotropicRemeshingOptions& options, int64_t index) +//{ +// // swap 32 +// auto swap32 = std::make_shared(*mesh, 0); +// swap32->add_invariant(std::make_shared(*mesh, 3)); +// swap32->add_invariant( +// std::make_shared(*mesh, pt_attribute.as())); +// +// swap32->collapse().add_invariant(invariant_separate_substructures); +// swap32->collapse().add_invariant(link_condition); +// swap32->collapse().set_new_attribute_strategy(pt_attribute, +// CollapseBasicStrategy::CopyOther); swap32->collapse().set_new_attribute_strategy( +// sizing_field_scalar_attribute, +// CollapseBasicStrategy::CopyOther); +// // swap32->add_invariant(inversion_invariant); +// swap32->split().set_new_attribute_strategy(pt_attribute); +// swap32->split().set_new_attribute_strategy(sizing_field_scalar_attribute); +// swap32->split().set_new_attribute_strategy( +// visited_edge_flag, +// wmtk::operations::SplitBasicStrategy::None, +// wmtk::operations::SplitRibBasicStrategy::None); +// swap32->collapse().set_new_attribute_strategy( +// visited_edge_flag, +// wmtk::operations::CollapseBasicStrategy::None); +// +// // swap32->split().add_transfer_strategy(amips_update); +// // swap32->collapse().add_transfer_strategy(amips_update); +// +// swap32->split().set_new_attribute_strategy( +// target_edge_length_attribute, +// wmtk::operations::SplitBasicStrategy::Copy, +// wmtk::operations::SplitRibBasicStrategy::Mean); +// swap32->collapse().set_new_attribute_strategy( +// target_edge_length_attribute, +// wmtk::operations::CollapseBasicStrategy::None); +// +// swap32->add_transfer_strategy(amips_update); +// +// // hack +// swap32->collapse().add_invariant(inversion_invariant); +// // swap32->collapse().add_invariant(envelope_invariant); +// +// for (const auto& attr : pass_through_attributes) { +// swap32->split().set_new_attribute_strategy( +// attr, +// wmtk::operations::SplitBasicStrategy::None, +// wmtk::operations::SplitRibBasicStrategy::None); +// swap32->collapse().set_new_attribute_strategy( +// attr, +// wmtk::operations::CollapseBasicStrategy::None); +// } +//} +std::shared_ptr tet_swap( + TetMesh& mesh, + const IsotropicRemeshingOptions& options) +{ + auto swap_all = std::make_shared(mesh); + //swap_all->add_operation(tet_swap32(mesh,options)); + swap_all->add_operation(tet_swap44(mesh,options)); + swap_all->add_operation(tet_swap56(mesh,options)); + + + ////////////////////////////////// + // energy filter flag + ////////////////////////////////// + auto& position_mesh = const_cast(options.position_attribute.mesh()); + auto energy_filter_handle = + position_mesh + .register_attribute("energy_filter", PrimitiveType::Vertex, 1, false, char(1)); + + auto energy_filter_accessor = position_mesh.create_accessor(energy_filter_handle); + + auto update_energy_filter_func = [](const Eigen::MatrixX& P) -> Eigen::VectorX { + return Eigen::VectorX::Constant(1, char(1)); + }; + auto energy_filter_update = + std::make_shared>( + energy_filter_handle, + options.position_attribute, + update_energy_filter_func); + + ////////////////////////////////// + // renew flags + ////////////////////////////////// + auto visited_edge_flag_handle = + position_mesh + .register_attribute("visited_edge", PrimitiveType::Edge, 1, false, char(1)); + + auto update_flag_func = [](const Eigen::MatrixX& P) -> Eigen::VectorX { + return Eigen::VectorX::Constant(1, char(1)); + }; + auto tag_update = + std::make_shared>( + visited_edge_flag_handle, + options.position_attribute, + update_flag_func); + + swap_all->add_transfer_strategy(tag_update); + swap_all->add_transfer_strategy(energy_filter_update); + return swap_all; +} +} // namespace wmtk::components::isotropic_remeshing::internal diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/setup_amips.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/setup_amips.hpp new file mode 100644 index 0000000000..dd94aa14a2 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/internal/setup_amips.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace wmtk::operations { + template + class SingleAttributeTransferStrategy; +} +namespace wmtk::components::isotropic_remeshing::internal { + + std::shared_ptr>( + amips_handle, + position_handle, + compute_amips); +} diff --git a/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp new file mode 100644 index 0000000000..38ff88e9d3 --- /dev/null +++ b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp @@ -0,0 +1,14 @@ +#include "isotropic_remeshing.hpp" + +#include "IsotropicRemeshing.hpp" + +namespace wmtk::components::isotropic_remeshing { + + +void isotropic_remeshing(const IsotropicRemeshingOptions& options) +{ + assert(options.position_attribute.is_valid()); + IsotropicRemeshing app(options); + app.run(); +} +} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/isotropic_remeshing.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/isotropic_remeshing.hpp similarity index 100% rename from components/isotropic_remeshing/wmtk/components/isotropic_remeshing/isotropic_remeshing.hpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/isotropic_remeshing.hpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/CMakeLists.txt b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/CMakeLists.txt similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/CMakeLists.txt rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/CMakeLists.txt diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/internal/PeriodicOptimizationOptions.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/PeriodicOptimizationOptions.hpp similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/internal/PeriodicOptimizationOptions.hpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/PeriodicOptimizationOptions.hpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/internal/periodic_optimization.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/periodic_optimization.cpp similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/internal/periodic_optimization.cpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/periodic_optimization.cpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/internal/periodic_optimization.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/periodic_optimization.hpp similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/internal/periodic_optimization.hpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/internal/periodic_optimization.hpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization.cpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization.cpp similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization.cpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization.cpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization.hpp b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization.hpp similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization.hpp rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization.hpp diff --git a/components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization_spec.json b/components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization_spec.json similarity index 100% rename from components/periodic_optimization/wmtk/components/periodic_optimization/periodic_optimization_spec.json rename to components/isotropic_remeshing/src/wmtk/components/isotropic_remeshing/periodic_optimization/periodic_optimization_spec.json diff --git a/components/isotropic_remeshing/tests/CMakeLists.txt b/components/isotropic_remeshing/tests/CMakeLists.txt new file mode 100644 index 0000000000..1dcac53abc --- /dev/null +++ b/components/isotropic_remeshing/tests/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_component_test(wmtk::${COMPONENT_NAME} basic_run.cpp ) diff --git a/components/isotropic_remeshing/tests/basic_run.cpp b/components/isotropic_remeshing/tests/basic_run.cpp new file mode 100644 index 0000000000..b1146fa21d --- /dev/null +++ b/components/isotropic_remeshing/tests/basic_run.cpp @@ -0,0 +1,24 @@ +#include + +#include +#include +#include +#include +TEST_CASE("component_isotropic_remeshing", "[components][isotropic_remeshing]") +{ + + auto mptr = std::make_shared(wmtk::tests::ten_triangles_with_position(3)); + + + wmtk::components::isotropic_remeshing::IsotropicRemeshingOptions opts; + opts.position_attribute = mptr->get_attribute_handle("vertices", wmtk::PrimitiveType::Vertex); + + opts.edge_swap_mode = wmtk::components::isotropic_remeshing::EdgeSwapMode::Valence; + + opts.envelope_size = 1e-3; + opts.length_rel = 1e-1; + opts.fix_uv_seam = false; + + wmtk::components::isotropic_remeshing::isotropic_remeshing(opts); + spdlog::info("hi"); +} diff --git a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/CMakeLists.txt b/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/CMakeLists.txt deleted file mode 100644 index f904476d86..0000000000 --- a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -set(COMPONENT_NAME isotropic_remeshing) -add_component(${COMPONENT_NAME}) -if(NOT ${WMTK_ENABLE_COMPONENT_${COMPONENT_NAME}}) - return() -endif() - -set(SRC_FILES - IsotropicRemeshingOptions.cpp - IsotropicRemeshingOptions.hpp - isotropic_remeshing.hpp - isotropic_remeshing.cpp - ) - - -#CURRENT_COMPONENT_LIB_NAME is set from the main cmake -target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) -target_link_libraries(wmtk_${COMPONENT_NAME} PRIVATE nlohmann_json) diff --git a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp b/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp deleted file mode 100644 index c5829396c1..0000000000 --- a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include "IsotropicRemeshingOptions.hpp" - - -namespace wmtk::components::isotropic_remeshing { - namespace { - -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( - IsotropicRemeshingOptions, - iterations , - length_abs , - length_rel , - lock_boundary , - use_for_periodic , - dont_disable_split , - fix_uv_seam ) - } - void IsotropicRemeshingOptions::load_json(const nlohmann::json& js) { - - from_json(js,*this); - } - void IsotropicRemeshingOptions::write_json(nlohmann::json& js) const { - to_json(js,*this); - } -} diff --git a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp b/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp deleted file mode 100644 index daf4957649..0000000000 --- a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/IsotropicRemeshingOptions.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include -#include -#include -#include - - -namespace wmtk::components::isotropic_remeshing { - - -struct IsotropicRemeshingOptions -{ - // std::shared_ptr input; - wmtk::attribute::MeshAttributeHandle position_attribute; - std::optional inversion_position_attribute; - std::vector other_position_attributes; - - std::vector pass_through_attributes; - int64_t iterations = 10; - double length_abs = 0; - double length_rel = 0; - bool lock_boundary = true; - bool use_for_periodic = false; - bool dont_disable_split = false; - bool fix_uv_seam = true; - - void load_json(const nlohmann::json& js); - void write_json(nlohmann::json& js) const; -}; - - -} // namespace wmtk::components::isotropic_remeshing diff --git a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp b/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp deleted file mode 100644 index e70bc5d1ea..0000000000 --- a/components/isotropic_remeshing/wmtk/components/isotropic_remeshing/isotropic_remeshing.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#include "isotropic_remeshing.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 "IsotropicRemeshingOptions.hpp" - -namespace wmtk::components::isotropic_remeshing { -// compute the length relative to the bounding box diagonal -double relative_to_absolute_length( - const attribute::MeshAttributeHandle& position, - const double length_rel) -{ - auto pos = position.mesh().create_const_accessor(position); - const auto vertices = position.mesh().get_all(PrimitiveType::Vertex); - Eigen::AlignedBox bbox(pos.dimension()); - - - for (const auto& v : vertices) { - bbox.extend(pos.const_vector_attribute(v)); - } - - const double diag_length = bbox.sizes().norm(); - - return length_rel * diag_length; -} - - -void isotropic_remeshing(const IsotropicRemeshingOptions& options) -{ - using namespace internal; - - - auto position = options.position_attribute; - - if (position.mesh().top_simplex_type() != PrimitiveType::Triangle) { - log_and_throw_error( - "isotropic remeshing works only for triangle meshes: {}", - primitive_type_name(position.mesh().top_simplex_type())); - } - - auto pass_through_attributes = options.pass_through_attributes; - auto other_positions = options.other_position_attributes; - - double length = options.length_abs; - if (options.length_abs < 0) { - if (options.length_rel < 0) { - throw std::runtime_error("Either absolute or relative length must be set!"); - } - length = relative_to_absolute_length(position, options.length_rel); - } - - // clear attributes - std::vector keeps = pass_through_attributes; - keeps.emplace_back(position); - keeps.insert(keeps.end(), other_positions.begin(), other_positions.end()); - - // TODO: brig me back! - // mesh_in->clear_attributes(keeps); - - // gather handles again as they were invalidated by clear_attributes - // positions = utils::get_attributes(cache, *mesh_in, - // options.position_attribute); assert(positions.size() == 1); position = - // positions.front(); pass_through_attributes = utils::get_attributes(cache, - // *mesh_in, options.pass_through_attributes); - - std::optional position_for_inversion = - options.inversion_position_attribute; - - - assert(dynamic_cast(&position.mesh()) != nullptr); - - TriMesh& mesh = static_cast(position.mesh()); - - const double length_min = (4. / 5.) * length; - const double length_max = (4. / 3.) * length; - - std::vector positions = other_positions; - positions.push_back(position); - - auto invariant_link_condition = - std::make_shared(mesh); - - auto invariant_min_edge_length = std::make_shared( - mesh, - position.as(), - length_max * length_max); - - auto invariant_max_edge_length = std::make_shared( - mesh, - position.as(), - length_min * length_min); - - auto invariant_interior_edge = std::make_shared(mesh); - auto invariant_interior_vertex = std::make_shared(mesh); - - auto set_all_invariants = [&](auto&& m) { - invariant_interior_edge->add( - std::make_shared(m, PrimitiveType::Edge)); - invariant_interior_vertex->add( - std::make_shared(m, PrimitiveType::Vertex)); - }; - multimesh::MultiMeshVisitor visitor(set_all_invariants); - visitor.execute_from_root(mesh); - - auto invariant_valence_improve = - std::make_shared(mesh); - - auto invariant_mm_map = std::make_shared(mesh); - - auto update_position_func = [](const Eigen::MatrixXd& P) -> Eigen::VectorXd { - return P.col(0); - }; - std::shared_ptr> - update_position; - - if (!options.other_position_attributes.empty()) { - update_position = - std::make_shared>( - other_positions.front(), - position, - update_position_func); - } - - using namespace operations; - - assert(mesh.is_connectivity_valid()); - - std::vector> ops; - - // split - wmtk::logger().debug("Configure isotropic remeshing split"); - auto op_split = std::make_shared(mesh); - op_split->add_invariant(invariant_min_edge_length); - if (options.lock_boundary && !options.use_for_periodic && !options.dont_disable_split) { - op_split->add_invariant(invariant_interior_edge); - } - for (auto& p : positions) { - op_split->set_new_attribute_strategy( - p, - SplitBasicStrategy::None, - SplitRibBasicStrategy::Mean); - } - for (const auto& attr : pass_through_attributes) { - op_split->set_new_attribute_strategy(attr); - } - assert(op_split->attribute_new_all_configured()); - ops.push_back(op_split); - - - - ////////////////////////////////////////// - // collapse - wmtk::logger().debug("Configure isotropic remeshing collapse"); - auto op_collapse = std::make_shared(mesh); - op_collapse->add_invariant(invariant_link_condition); - if (position_for_inversion) { - op_collapse->add_invariant(std::make_shared>( - position_for_inversion.value().mesh(), - position_for_inversion.value().as())); - } - - op_collapse->add_invariant(invariant_max_edge_length); - op_collapse->add_invariant(invariant_mm_map); - - // hack for uv - if (options.fix_uv_seam) { - op_collapse->add_invariant( - std::make_shared(mesh, other_positions.front().mesh())); - } - - if (options.lock_boundary && !options.use_for_periodic) { - op_collapse->add_invariant(invariant_interior_edge); - // set collapse towards boundary - for (auto& p : positions) { - auto tmp = std::make_shared>(p); - tmp->set_strategy(CollapseBasicStrategy::Mean); - tmp->set_simplex_predicate(BasicSimplexPredicate::IsInterior); - op_collapse->set_new_attribute_strategy(p, tmp); - } - } else if (options.use_for_periodic) { - op_collapse->add_invariant( - std::make_shared(mesh, mesh.get_multi_mesh_root())); - for (auto& p : positions) { - op_collapse->set_new_attribute_strategy(p, CollapseBasicStrategy::Mean); - } - } else { - for (auto& p : positions) { - op_collapse->set_new_attribute_strategy(p, CollapseBasicStrategy::Mean); - } - } - - - for (const auto& attr : pass_through_attributes) { - op_collapse->set_new_attribute_strategy(attr); - } - assert(op_collapse->attribute_new_all_configured()); - ops.push_back(op_collapse); - - - ////////////////////////////////////////// - // swap - wmtk::logger().debug("Configure isotropic remeshing swap"); - auto op_swap = std::make_shared(mesh); - op_swap->add_invariant(invariant_interior_edge); - - // hack for uv - if (options.fix_uv_seam) { - op_swap->add_invariant( - std::make_shared(mesh, other_positions.front().mesh())); - } - - op_swap->add_invariant(invariant_valence_improve); - op_swap->collapse().add_invariant(invariant_link_condition); - op_swap->collapse().add_invariant(invariant_mm_map); - for (auto& p : positions) { - op_swap->split().set_new_attribute_strategy( - p, - SplitBasicStrategy::None, - SplitRibBasicStrategy::Mean); - } - if (position_for_inversion) { - op_swap->collapse().add_invariant(std::make_shared>( - position_for_inversion.value().mesh(), - position_for_inversion.value().as())); - } - - for (auto& p : positions) - op_swap->collapse().set_new_attribute_strategy(p, CollapseBasicStrategy::CopyOther); - for (const auto& attr : pass_through_attributes) { - op_swap->split().set_new_attribute_strategy(attr); - op_swap->collapse().set_new_attribute_strategy(attr); - } - assert(op_swap->split().attribute_new_all_configured()); - assert(op_swap->collapse().attribute_new_all_configured()); - ops.push_back(op_swap); - - - ////////////////////////////////////////// - // smooth - auto op_smooth = std::make_shared(mesh); - if (position.dimension() == 3) { - op_smooth->set_function(VertexTangentialLaplacianSmooth(position)); - } else { - op_smooth->set_function(VertexLaplacianSmooth(position)); - } - - if (options.lock_boundary) { - op_smooth->add_invariant(invariant_interior_vertex); - } - - // hack for uv - if (options.fix_uv_seam) { - op_smooth->add_invariant( - std::make_shared(mesh, other_positions.front().mesh())); - } - - if (position_for_inversion) { - op_smooth->add_invariant(std::make_shared>( - position_for_inversion.value().mesh(), - position_for_inversion.value().as())); - } - - if (update_position) op_smooth->add_transfer_strategy(update_position); - ops.push_back(op_smooth); - - - ////////////////////////////////////////// - Scheduler scheduler; - for (long i = 0; i < options.iterations; ++i) { - wmtk::logger().info("Iteration {}", i); - - SchedulerStats pass_stats; - for (size_t j = 0; j < ops.size(); ++j) { - const auto& op = ops[j]; - pass_stats += scheduler.run_operation_on_all(*op); - } - - multimesh::consolidate(mesh); - - 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); - } -} -} // namespace wmtk::components::isotropic_remeshing 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/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..11c1a58c89 100644 --- a/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp +++ b/components/multimesh/src/wmtk/components/multimesh/NamedMultiMesh.hpp @@ -4,7 +4,10 @@ namespace wmtk { class Mesh; +namespace attribute { +class MeshAttributeHandle; } +} // namespace wmtk namespace wmtk::components::multimesh { @@ -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/axis_aligned_fusion.cpp b/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.cpp index b801a3f501..3dd3eaaef3 100644 --- a/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.cpp +++ b/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.cpp @@ -13,13 +13,30 @@ namespace wmtk::components::multimesh { - std::shared_ptr axis_aligned_fusion(const Mesh& mesh, const std::vector& operating_axis, double eps) { + + return axis_aligned_fusion(mesh.get_attribute_handle("vertices", PrimitiveType::Vertex), operating_axis, eps); + + +} + +std::shared_ptr +axis_aligned_fusion(const attribute::MeshAttributeHandle& position_handle, const std::vector& operating_axis, double eps) +{ + Mesh& mesh = const_cast(position_handle.mesh()); // get mesh dimension and checks int64_t mesh_dim = mesh.top_cell_dimension(); + if( mesh_dim != position_handle.dimension()) { + throw std::runtime_error(fmt::format("Can only perform axis aligned fusion on a {0}-dimensional position on a {0}-mesh, got a {1}-dimensional position", + mesh_dim, position_handle.dimension())); + } + if(mesh_dim != operating_axis.size()) { + throw std::runtime_error(fmt::format("Can only perform axis aligned fusion on a {0} entries in the operating_axis mask on a {0}-mesh, got a {1} values", + mesh_dim, operating_axis.size())); + } std::map> names; @@ -27,15 +44,13 @@ axis_aligned_fusion(const Mesh& mesh, const std::vector& operating_axis, d switch (mesh_dim) { case (2): { - if (operating_axis[2]) { - wmtk::logger().warn("Fusion on Z axis is not supported for 2D mesh."); - } MatrixX V; MatrixX FV; wmtk::utils::EigenMatrixWriter writer; mesh.serialize(writer); + // TODO: make this the mah passed in writer.get_position_matrix(V); writer.get_FV_matrix(FV); diff --git a/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.hpp b/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.hpp index d8a6c8a360..f7636fd208 100644 --- a/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.hpp +++ b/components/multimesh/src/wmtk/components/multimesh/axis_aligned_fusion.hpp @@ -5,6 +5,9 @@ namespace wmtk { class Mesh; +namespace attribute { + class MeshAttributeHandle; +} } // namespace wmtk namespace wmtk::components::multimesh { @@ -12,5 +15,8 @@ namespace wmtk::components::multimesh { // void axis_aligned_fusion(Mesh& m, const std::vector& axes_to_fuse, double eps = 1e-10); std::shared_ptr axis_aligned_fusion(const Mesh& m, const std::vector& axes_to_fuse, double eps = 1e-10); +std::shared_ptr +axis_aligned_fusion(const attribute::MeshAttributeHandle& m, const std::vector& axes_to_fuse, double eps = 1e-10); + } // namespace wmtk::components::multimesh 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 diff --git a/components/procedural/wmtk/components/procedural/CMakeLists.txt b/components/procedural/wmtk/components/procedural/CMakeLists.txt index b7ae2ebdae..b6153b6a2d 100644 --- a/components/procedural/wmtk/components/procedural/CMakeLists.txt +++ b/components/procedural/wmtk/components/procedural/CMakeLists.txt @@ -5,6 +5,7 @@ if(NOT ${WMTK_ENABLE_COMPONENT_${COMPONENT_NAME}}) endif() set(SRC_FILES + ProceduralOptions.cpp ProceduralOptions.hpp Grid2Options.hpp Grid2Options.cpp diff --git a/components/procedural/wmtk/components/procedural/DiskOptions.cpp b/components/procedural/wmtk/components/procedural/DiskOptions.cpp index 5a648572ab..1dd3f3b275 100644 --- a/components/procedural/wmtk/components/procedural/DiskOptions.cpp +++ b/components/procedural/wmtk/components/procedural/DiskOptions.cpp @@ -46,4 +46,19 @@ std::shared_ptr make_mesh(const DiskOptions& opt) } return mptr; } + + void to_json(nlohmann::json& nlohmann_json_j, const DiskOptions& nlohmann_json_t) + { + nlohmann_json_j["size"] = nlohmann_json_t.size; + if (nlohmann_json_t.coordinates.has_value()) { + nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; + } + } + void from_json(const nlohmann::json& nlohmann_json_j, DiskOptions& nlohmann_json_t) + { + nlohmann_json_t.size = nlohmann_json_j["size"]; + if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { + nlohmann_json_t.coordinates = coords.get(); + } + } } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/DiskOptions.hpp b/components/procedural/wmtk/components/procedural/DiskOptions.hpp index 5c4f4afe1d..9807a1057e 100644 --- a/components/procedural/wmtk/components/procedural/DiskOptions.hpp +++ b/components/procedural/wmtk/components/procedural/DiskOptions.hpp @@ -27,19 +27,7 @@ class DiskOptions return {}; } } - friend void to_json(nlohmann::json& nlohmann_json_j, const DiskOptions& nlohmann_json_t) - { - nlohmann_json_j["size"] = nlohmann_json_t.size; - if (nlohmann_json_t.coordinates.has_value()) { - nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; - } - } - friend void from_json(const nlohmann::json& nlohmann_json_j, DiskOptions& nlohmann_json_t) - { - nlohmann_json_t.size = nlohmann_json_j["size"]; - if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { - nlohmann_json_t.coordinates = coords.get(); - } - } + friend void to_json(nlohmann::json& nlohmann_json_j, const DiskOptions& nlohmann_json_t); + friend void from_json(const nlohmann::json& nlohmann_json_j, DiskOptions& nlohmann_json_t); }; } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/Grid2Options.cpp b/components/procedural/wmtk/components/procedural/Grid2Options.cpp index d50ee7f1ea..88e5b0f103 100644 --- a/components/procedural/wmtk/components/procedural/Grid2Options.cpp +++ b/components/procedural/wmtk/components/procedural/Grid2Options.cpp @@ -88,4 +88,50 @@ std::shared_ptr make_mesh(const Grid2Options& opt) throw std::runtime_error("failed to select a tiling type"); return nullptr; } + + void to_json(nlohmann::json& nlohmann_json_j, const Grid2Options& nlohmann_json_t) + { + nlohmann_json_j["tiling"] = Grid2Options::tiling_names[static_cast(nlohmann_json_t.tiling_type)]; + if (nlohmann_json_t.coordinates.has_value()) { + nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; + } + nlohmann_json_j["dimensions"] = nlohmann_json_t.dimensions; + { + const auto& b = nlohmann_json_t.cycles; + std::array bs{{b[0], b[1]}}; + nlohmann_json_j["cycles"] = bs; + } + } + void from_json(const nlohmann::json& nlohmann_json_j, Grid2Options& nlohmann_json_t) + { + nlohmann_json_t.dimensions = nlohmann_json_j["dimensions"].get>(); + + { + const std::string tiling = nlohmann_json_j["tiling"]; + bool found = false; + for (size_t j = 0; j < Grid2Options::tiling_names.size(); ++j) { + if (tiling == Grid2Options::tiling_names[j]) { + found = true; + nlohmann_json_t.tiling_type = static_cast(j); + } + } + if (!found) { + throw std::runtime_error(fmt::format( + "Tiling type was not found, got [{}], expected one of {{[{}]}}", + tiling, + fmt::join(Grid2Options::tiling_names, "],["))); + } + } + { + auto& b = nlohmann_json_t.cycles; + const auto& c = nlohmann_json_j["cycles"]; + for (int j = 0; j < 2; ++j) { + b[j] = c[j]; + } + } + if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { + if (nlohmann_json_j["coordinates"]["spacing"][0] > 0) + nlohmann_json_t.coordinates = coords.get(); + } + } } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/Grid2Options.hpp b/components/procedural/wmtk/components/procedural/Grid2Options.hpp index b96f0a2c6c..a3ec020f58 100644 --- a/components/procedural/wmtk/components/procedural/Grid2Options.hpp +++ b/components/procedural/wmtk/components/procedural/Grid2Options.hpp @@ -27,50 +27,7 @@ class Grid2Options std::optional coordinates; std::optional get_coordinate_name() const { if(coordinates.has_value()) { return coordinates.value().name;} else { return {}; } } - friend void to_json(nlohmann::json& nlohmann_json_j, const Grid2Options& nlohmann_json_t) - { - nlohmann_json_j["tiling"] = tiling_names[static_cast(nlohmann_json_t.tiling_type)]; - if (nlohmann_json_t.coordinates.has_value()) { - nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; - } - nlohmann_json_j["dimensions"] = nlohmann_json_t.dimensions; - { - const auto& b = nlohmann_json_t.cycles; - std::array bs{{b[0], b[1]}}; - nlohmann_json_j["cycles"] = bs; - } - } - friend void from_json(const nlohmann::json& nlohmann_json_j, Grid2Options& nlohmann_json_t) - { - nlohmann_json_t.dimensions = nlohmann_json_j["dimensions"].get>(); - - { - const std::string tiling = nlohmann_json_j["tiling"]; - bool found = false; - for (size_t j = 0; j < tiling_names.size(); ++j) { - if (tiling == tiling_names[j]) { - found = true; - nlohmann_json_t.tiling_type = static_cast(j); - } - } - if (!found) { - throw std::runtime_error(fmt::format( - "Tiling type was not found, got [{}], expected one of {{[{}]}}", - tiling, - fmt::join(tiling_names, "],["))); - } - } - { - auto& b = nlohmann_json_t.cycles; - const auto& c = nlohmann_json_j["cycles"]; - for (int j = 0; j < 2; ++j) { - b[j] = c[j]; - } - } - if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { - if (nlohmann_json_j["coordinates"]["spacing"][0] > 0) - nlohmann_json_t.coordinates = coords.get(); - } - } + friend void to_json(nlohmann::json& nlohmann_json_j, const Grid2Options& nlohmann_json_t); + friend void from_json(const nlohmann::json& nlohmann_json_j, Grid2Options& nlohmann_json_t); }; } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/Grid3Options.cpp b/components/procedural/wmtk/components/procedural/Grid3Options.cpp index c154d87400..2c6b8b7d7a 100644 --- a/components/procedural/wmtk/components/procedural/Grid3Options.cpp +++ b/components/procedural/wmtk/components/procedural/Grid3Options.cpp @@ -7,7 +7,7 @@ namespace wmtk::components::procedural { -const std::array Grid3Options::tiling_names = {{"bcc", "freudenthal"}}; +const std::array Grid3Options::Grid3Options::tiling_names = {{"bcc", "freudenthal"}}; namespace { std::shared_ptr make_freudenthal_mesh(const Grid3Options& opt) @@ -103,4 +103,49 @@ std::shared_ptr make_mesh(const Grid3Options& opt) throw std::runtime_error("failed to select a tiling type"); return nullptr; } + + void to_json(nlohmann::json& nlohmann_json_j, const Grid3Options& nlohmann_json_t) + { + nlohmann_json_j["tiling"] = Grid3Options::tiling_names[static_cast(nlohmann_json_t.tiling_type)]; + if (nlohmann_json_t.coordinates.has_value()) { + nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; + } + nlohmann_json_j["dimensions"] = nlohmann_json_t.dimensions; + { + const auto& b = nlohmann_json_t.cycles; + std::array bs{{b[0], b[1], b[2]}}; + nlohmann_json_j["cycles"] = bs; + } + } + void from_json(const nlohmann::json& nlohmann_json_j, Grid3Options& nlohmann_json_t) + { + nlohmann_json_t.dimensions = nlohmann_json_j["dimensions"].get>(); + { + const std::string tiling = nlohmann_json_j["tiling"]; + bool found = false; + for (size_t j = 0; j < Grid3Options::tiling_names.size(); ++j) { + if (tiling == Grid3Options::tiling_names[j]) { + found = true; + nlohmann_json_t.tiling_type = static_cast(j); + } + } + if (!found) { + throw std::runtime_error(fmt::format( + "Tiling type was not found, got [{}], expected one of {{[{}]}}", + tiling, + fmt::join(Grid3Options::tiling_names, "],["))); + } + } + { + auto& b = nlohmann_json_t.cycles; + const auto& c = nlohmann_json_j["cycles"]; + for (int j = 0; j < 3; ++j) { + b[j] = c[j]; + } + } + if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { + if (nlohmann_json_j["coordinates"]["spacing"][0] > 0) + nlohmann_json_t.coordinates = coords.get(); + } + } } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/Grid3Options.hpp b/components/procedural/wmtk/components/procedural/Grid3Options.hpp index 3a362fd0c0..72a20a4c7d 100644 --- a/components/procedural/wmtk/components/procedural/Grid3Options.hpp +++ b/components/procedural/wmtk/components/procedural/Grid3Options.hpp @@ -23,59 +23,8 @@ class Grid3Options NLOHMANN_DEFINE_TYPE_INTRUSIVE(Coordinates, name, spacing); }; std::optional coordinates; - std::optional get_coordinate_name() const - { - if (coordinates.has_value()) { - return coordinates.value().name; - } else { - return {}; - } - } - friend void to_json(nlohmann::json& nlohmann_json_j, const Grid3Options& nlohmann_json_t) - { - nlohmann_json_j["tiling"] = tiling_names[static_cast(nlohmann_json_t.tiling_type)]; - if (nlohmann_json_t.coordinates.has_value()) { - nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; - } - nlohmann_json_j["dimensions"] = nlohmann_json_t.dimensions; - { - const auto& b = nlohmann_json_t.cycles; - std::array bs{{b[0], b[1], b[2]}}; - nlohmann_json_j["cycles"] = bs; - } - } - friend void from_json(const nlohmann::json& nlohmann_json_j, Grid3Options& nlohmann_json_t) - { - nlohmann_json_t.dimensions = nlohmann_json_j["dimensions"].get>(); - { - const std::string tiling = nlohmann_json_j["tiling"]; - bool found = false; - for (size_t j = 0; j < tiling_names.size(); ++j) { - if (tiling == tiling_names[j]) { - found = true; - nlohmann_json_t.tiling_type = static_cast(j); - } - } - if (!found) { - throw std::runtime_error(fmt::format( - "Tiling type was not found, got [{}], expected one of {{[{}]}}", - tiling, - fmt::join(tiling_names, "],["))); - } - } - { - auto& b = nlohmann_json_t.cycles; - const auto& c = nlohmann_json_j["cycles"]; - for (int j = 0; j < 3; ++j) { - b[j] = c[j]; - } - } - if (nlohmann_json_j.contains("coordinates")) { - if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { - if (nlohmann_json_j["coordinates"]["spacing"][0] > 0) - nlohmann_json_t.coordinates = coords.get(); - } - } - } + std::optional get_coordinate_name() const { if(coordinates.has_value()) { return coordinates.value().name;} else { return {}; } } + friend void to_json(nlohmann::json& nlohmann_json_j, const Grid3Options& nlohmann_json_t); + friend void from_json(const nlohmann::json& nlohmann_json_j, Grid3Options& nlohmann_json_t); }; } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/GridOptions.hpp b/components/procedural/wmtk/components/procedural/GridOptions.hpp index 21bb941956..419e3892c0 100644 --- a/components/procedural/wmtk/components/procedural/GridOptions.hpp +++ b/components/procedural/wmtk/components/procedural/GridOptions.hpp @@ -9,6 +9,7 @@ namespace wmtk::components::procedural { class GridOptions { +public: constexpr static auto name() -> std::string_view { return "grid"; } public: diff --git a/components/procedural/wmtk/components/procedural/ProceduralOptions.cpp b/components/procedural/wmtk/components/procedural/ProceduralOptions.cpp new file mode 100644 index 0000000000..fbaeda3c25 --- /dev/null +++ b/components/procedural/wmtk/components/procedural/ProceduralOptions.cpp @@ -0,0 +1,28 @@ +#include "ProceduralOptions.hpp" + + +namespace wmtk::components::procedural { + + void to_json(nlohmann::json& nlohmann_json_j, const ProceduralOptions& nlohmann_json_t) + { + std::visit( + [&](const auto& s) { nlohmann_json_j["settings"] = s; }, + nlohmann_json_t.settings); + } + void from_json(const nlohmann::json& nlohmann_json_j, ProceduralOptions& nlohmann_json_t) + { + if (nlohmann_json_j.contains("triangle_fan")) { + const auto& settings_js = nlohmann_json_j["triangle_fan"]; + nlohmann_json_t.settings = settings_js.get(); + } else if (nlohmann_json_j.contains("grid")) { + const auto& settings_js = nlohmann_json_j["grid"]; + nlohmann_json_t.settings = settings_js.get(); + } else if (nlohmann_json_j.contains("disk")) { + const auto& settings_js = nlohmann_json_j["disk"]; + nlohmann_json_t.settings = settings_js.get(); + + } else { + throw std::runtime_error(fmt::format("Unknown procedural mesh mesh_type")); + } + } +} diff --git a/components/procedural/wmtk/components/procedural/ProceduralOptions.hpp b/components/procedural/wmtk/components/procedural/ProceduralOptions.hpp index 5144496d63..cf324fe32b 100644 --- a/components/procedural/wmtk/components/procedural/ProceduralOptions.hpp +++ b/components/procedural/wmtk/components/procedural/ProceduralOptions.hpp @@ -12,31 +12,12 @@ namespace wmtk::components::procedural { struct ProceduralOptions { - std::string name; std::variant settings; - friend void to_json(nlohmann::json& nlohmann_json_j, const ProceduralOptions& nlohmann_json_t) - { - std::visit( - [&](const auto& s) { nlohmann_json_j["settings"] = s; }, - nlohmann_json_t.settings); - } - friend void from_json(const nlohmann::json& nlohmann_json_j, ProceduralOptions& nlohmann_json_t) - { - if (nlohmann_json_j.contains("triangle_fan")) { - const auto& settings_js = nlohmann_json_j["triangle_fan"]; - nlohmann_json_t.settings = settings_js.get(); - } else if (nlohmann_json_j.contains("grid")) { - const auto& settings_js = nlohmann_json_j["grid"]; - nlohmann_json_t.settings = settings_js.get(); - } else if (nlohmann_json_j.contains("disk")) { - const auto& settings_js = nlohmann_json_j["disk"]; - nlohmann_json_t.settings = settings_js.get(); - - } else { - throw std::runtime_error(fmt::format("Unknown procedural mesh mesh_type")); - } - } + friend void to_json(nlohmann::json& nlohmann_json_j, const ProceduralOptions& nlohmann_json_t); + friend void from_json( + const nlohmann::json& nlohmann_json_j, + ProceduralOptions& nlohmann_json_t); }; diff --git a/components/procedural/wmtk/components/procedural/TriangleFanOptions.cpp b/components/procedural/wmtk/components/procedural/TriangleFanOptions.cpp index 7249a1177c..1fc15b1f6a 100644 --- a/components/procedural/wmtk/components/procedural/TriangleFanOptions.cpp +++ b/components/procedural/wmtk/components/procedural/TriangleFanOptions.cpp @@ -48,4 +48,37 @@ std::shared_ptr make_mesh(const TriangleFanOptions& opt) } return mptr; } + + void to_json(nlohmann::json& nlohmann_json_j, const TriangleFanOptions::Coordinates& nlohmann_json_t) + { + NLOHMANN_JSON_EXPAND( + NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, name, radius, center, degrees)); + } + void from_json(const nlohmann::json& nlohmann_json_j, TriangleFanOptions::Coordinates& nlohmann_json_t) + { + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, name, radius, center)); + assert(nlohmann_json_j.contains("degrees")); + if (const auto& deg = nlohmann_json_j["degrees"]; deg.is_number()) { + nlohmann_json_t.degrees = std::array{{0.0, double(deg)}}; + } else { + nlohmann_json_t.degrees = deg.get>(); + } + } + + void to_json(nlohmann::json& nlohmann_json_j, const TriangleFanOptions& nlohmann_json_t) + { + nlohmann_json_j["size"] = nlohmann_json_t.size; + if (nlohmann_json_t.coordinates.has_value()) { + nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; + } + } + void from_json( + const nlohmann::json& nlohmann_json_j, + TriangleFanOptions& nlohmann_json_t) + { + nlohmann_json_t.size = nlohmann_json_j["size"]; + if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { + nlohmann_json_t.coordinates = coords.get(); + } + } } // namespace wmtk::components::procedural diff --git a/components/procedural/wmtk/components/procedural/TriangleFanOptions.hpp b/components/procedural/wmtk/components/procedural/TriangleFanOptions.hpp index 457e8fd30e..dce1eba613 100644 --- a/components/procedural/wmtk/components/procedural/TriangleFanOptions.hpp +++ b/components/procedural/wmtk/components/procedural/TriangleFanOptions.hpp @@ -18,21 +18,8 @@ class TriangleFanOptions double radius; std::array degrees; // in degrees - friend void to_json(nlohmann::json& nlohmann_json_j, const Coordinates& nlohmann_json_t) - { - NLOHMANN_JSON_EXPAND( - NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, name, radius, center, degrees)); - } - friend void from_json(const nlohmann::json& nlohmann_json_j, Coordinates& nlohmann_json_t) - { - NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, name, radius, center)); - assert(nlohmann_json_j.contains("degrees")); - if (const auto& deg = nlohmann_json_j["degrees"]; deg.is_number()) { - nlohmann_json_t.degrees = std::array{{0.0, double(deg)}}; - } else { - nlohmann_json_t.degrees = deg.get>(); - } - } + friend void to_json(nlohmann::json& nlohmann_json_j, const Coordinates& nlohmann_json_t); + friend void from_json(const nlohmann::json& nlohmann_json_j, Coordinates& nlohmann_json_t); }; std::optional coordinates; @@ -44,21 +31,9 @@ class TriangleFanOptions return {}; } } - friend void to_json(nlohmann::json& nlohmann_json_j, const TriangleFanOptions& nlohmann_json_t) - { - nlohmann_json_j["size"] = nlohmann_json_t.size; - if (nlohmann_json_t.coordinates.has_value()) { - nlohmann_json_j["coordinates"] = *nlohmann_json_t.coordinates; - } - } + friend void to_json(nlohmann::json& nlohmann_json_j, const TriangleFanOptions& nlohmann_json_t); friend void from_json( const nlohmann::json& nlohmann_json_j, - TriangleFanOptions& nlohmann_json_t) - { - nlohmann_json_t.size = nlohmann_json_j["size"]; - if (const auto& coords = nlohmann_json_j["coordinates"]; !coords.is_null()) { - nlohmann_json_t.coordinates = coords.get(); - } - } + TriangleFanOptions& nlohmann_json_t); }; } // namespace wmtk::components::procedural diff --git a/components/regular_space/wmtk/components/regular_space/CMakeLists.txt b/components/regular_space/wmtk/components/regular_space/CMakeLists.txt index 7464a888e2..a02d348ab1 100644 --- a/components/regular_space/wmtk/components/regular_space/CMakeLists.txt +++ b/components/regular_space/wmtk/components/regular_space/CMakeLists.txt @@ -1,13 +1,18 @@ +set(COMPONENT_NAME "regular_space") +add_component(${COMPONENT_NAME}) +if(NOT ${WMTK_ENABLE_COMPONENT_${COMPONENT_NAME}}) + return() +endif() set(SRC_FILES internal/RegularSpaceOptions.cpp internal/RegularSpaceOptions.hpp internal/RegularSpace.hpp internal/RegularSpace.cpp - regular_space.hpp - regular_space.cpp + #regular_space.hpp + #regular_space.cpp ) #CURRENT_COMPONENT_LIB_NAME is set from the main cmake -target_sources(${CURRENT_COMPONENT_LIB_NAME} PRIVATE ${SRC_FILES}) +target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) diff --git a/components/regular_space/wmtk/components/regular_space/internal/RegularSpace.cpp b/components/regular_space/wmtk/components/regular_space/internal/RegularSpace.cpp index aca2e5fb40..0d47145969 100644 --- a/components/regular_space/wmtk/components/regular_space/internal/RegularSpace.cpp +++ b/components/regular_space/wmtk/components/regular_space/internal/RegularSpace.cpp @@ -173,7 +173,7 @@ void RegularSpace::regularize_tags() break; } case PrimitiveType::Triangle: { // face split - composite::TriFaceSplit op_face_split(m_mesh); + composite::TriFaceSplit op_face_split(static_cast(m_mesh)); op_face_split.add_invariant(std::make_shared( m_mesh, std::get>(todo_handle.handle()))); @@ -209,7 +209,7 @@ void RegularSpace::regularize_tags() case PrimitiveType::Tetrahedron: { log_and_throw_error("Regular space component not implemented for TetMeshes."); } - default: log_and_throw_error("unknown primitive type: {}", ta.m_ptype); break; + default: log_and_throw_error("unknown primitive type: {}", primitive_type_name(ta.m_ptype)); break; } } } diff --git a/components/simplicial_embedding/wmtk/components/simplicial_embedding/internal/SimplicialEmbedding.cpp b/components/simplicial_embedding/wmtk/components/simplicial_embedding/internal/SimplicialEmbedding.cpp index 59a8a2c167..d49024dd96 100644 --- a/components/simplicial_embedding/wmtk/components/simplicial_embedding/internal/SimplicialEmbedding.cpp +++ b/components/simplicial_embedding/wmtk/components/simplicial_embedding/internal/SimplicialEmbedding.cpp @@ -197,7 +197,7 @@ void SimplicialEmbedding::regularize_tags(bool generate_simplicial_embedding) break; } case PrimitiveType::Triangle: { // face split - composite::TriFaceSplit op_face_split(m_mesh); + composite::TriFaceSplit op_face_split(static_cast(m_mesh)); op_face_split.add_invariant(std::make_shared( m_mesh, std::get>(todo_handle.handle()))); @@ -239,7 +239,7 @@ void SimplicialEmbedding::regularize_tags(bool generate_simplicial_embedding) break; } case PrimitiveType::Tetrahedron: { // tet split - composite::TetCellSplit op_tet_split(m_mesh); + composite::TetCellSplit op_tet_split(static_cast(m_mesh)); op_tet_split.add_invariant(std::make_shared( m_mesh, std::get>(todo_handle.handle()))); @@ -308,4 +308,4 @@ void SimplicialEmbedding::regularize_tags(bool generate_simplicial_embedding) } -} // namespace wmtk::components::internal \ No newline at end of file +} // namespace wmtk::components::internal diff --git a/components/utils/CMakeLists.txt b/components/utils/CMakeLists.txt index 9cfbf157d5..9c3784e844 100644 --- a/components/utils/CMakeLists.txt +++ b/components/utils/CMakeLists.txt @@ -1,28 +1,2 @@ - -set(SRC_FILES - wmtk/components/utils/ComponentOptionsBase.hpp - wmtk/components/utils/json_utils.hpp - wmtk/components/utils/json_utils.cpp - wmtk/components/utils/get_attributes.hpp - wmtk/components/utils/get_attributes.cpp - - wmtk/components/utils/resolve_path.cpp - wmtk/components/utils/resolve_path.hpp - wmtk/components/utils/json_macros.hpp - - wmtk/components/utils/PathResolver.cpp - wmtk/components/utils/PathResolver.hpp - ) - -add_library(wmtk_component_utils ${SRC_FILES}) -target_link_libraries(wmtk_component_utils PRIVATE wmtk::warnings wmtk::toolkit) -target_include_directories(wmtk_component_utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -# create alias target -add_library(wmtk::component_utils ALIAS wmtk_component_utils) - -# Group source files for IDEs -file(GLOB_RECURSE COMPONENTS_FILES_FOR_SOURCE_GROUP "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils/*.hpp") -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils" PREFIX "src" FILES ${COMPONENTS_FILES_FOR_SOURCE_GROUP}) - -set_target_properties(wmtk_component_utils PROPERTIES FOLDER wmtk_components) +set(COMPONENT_NAME utils) +add_subdirectory(src) diff --git a/components/utils/src/CMakeLists.txt b/components/utils/src/CMakeLists.txt new file mode 100644 index 0000000000..9cfbf157d5 --- /dev/null +++ b/components/utils/src/CMakeLists.txt @@ -0,0 +1,28 @@ + +set(SRC_FILES + wmtk/components/utils/ComponentOptionsBase.hpp + wmtk/components/utils/json_utils.hpp + wmtk/components/utils/json_utils.cpp + wmtk/components/utils/get_attributes.hpp + wmtk/components/utils/get_attributes.cpp + + wmtk/components/utils/resolve_path.cpp + wmtk/components/utils/resolve_path.hpp + wmtk/components/utils/json_macros.hpp + + wmtk/components/utils/PathResolver.cpp + wmtk/components/utils/PathResolver.hpp + ) + +add_library(wmtk_component_utils ${SRC_FILES}) +target_link_libraries(wmtk_component_utils PRIVATE wmtk::warnings wmtk::toolkit) +target_include_directories(wmtk_component_utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# create alias target +add_library(wmtk::component_utils ALIAS wmtk_component_utils) + +# Group source files for IDEs +file(GLOB_RECURSE COMPONENTS_FILES_FOR_SOURCE_GROUP "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils/*.hpp") +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/wmtk/components/utils" PREFIX "src" FILES ${COMPONENTS_FILES_FOR_SOURCE_GROUP}) + +set_target_properties(wmtk_component_utils PROPERTIES FOLDER wmtk_components) diff --git a/components/utils/wmtk/components/utils/ComponentOptionsBase.hpp b/components/utils/src/wmtk/components/utils/ComponentOptionsBase.hpp similarity index 100% rename from components/utils/wmtk/components/utils/ComponentOptionsBase.hpp rename to components/utils/src/wmtk/components/utils/ComponentOptionsBase.hpp diff --git a/components/utils/wmtk/components/utils/PathResolver.cpp b/components/utils/src/wmtk/components/utils/PathResolver.cpp similarity index 100% rename from components/utils/wmtk/components/utils/PathResolver.cpp rename to components/utils/src/wmtk/components/utils/PathResolver.cpp diff --git a/components/utils/wmtk/components/utils/PathResolver.hpp b/components/utils/src/wmtk/components/utils/PathResolver.hpp similarity index 100% rename from components/utils/wmtk/components/utils/PathResolver.hpp rename to components/utils/src/wmtk/components/utils/PathResolver.hpp diff --git a/components/utils/wmtk/components/utils/get_attributes.cpp b/components/utils/src/wmtk/components/utils/get_attributes.cpp similarity index 100% rename from components/utils/wmtk/components/utils/get_attributes.cpp rename to components/utils/src/wmtk/components/utils/get_attributes.cpp diff --git a/components/utils/wmtk/components/utils/get_attributes.hpp b/components/utils/src/wmtk/components/utils/get_attributes.hpp similarity index 100% rename from components/utils/wmtk/components/utils/get_attributes.hpp rename to components/utils/src/wmtk/components/utils/get_attributes.hpp diff --git a/components/utils/wmtk/components/utils/json_macros.hpp b/components/utils/src/wmtk/components/utils/json_macros.hpp similarity index 74% rename from components/utils/wmtk/components/utils/json_macros.hpp rename to components/utils/src/wmtk/components/utils/json_macros.hpp index 546c7db2bc..960e88f64d 100644 --- a/components/utils/wmtk/components/utils/json_macros.hpp +++ b/components/utils/src/wmtk/components/utils/json_macros.hpp @@ -27,4 +27,11 @@ { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +// Before declaring assignment with a default you need to construct a default object +#define WMTK_NLOHMANN_JSON_DECLARE_DEFAULT_OBJECT(Type)\ + const Type nlohmann_json_default_obj{}; + +#define WMTK_NLOHMANN_ASSIGN_TYPE_FROM_JSON_WITH_DEFAULT(...) \ + { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }// if nlohmann_json_default_obj doesn't exist call WMTK_NLOHMANN_JSON_DECLARE_DEFAULT_OBJECT + diff --git a/components/utils/wmtk/components/utils/json_serialize_enum.hpp b/components/utils/src/wmtk/components/utils/json_serialize_enum.hpp similarity index 100% rename from components/utils/wmtk/components/utils/json_serialize_enum.hpp rename to components/utils/src/wmtk/components/utils/json_serialize_enum.hpp diff --git a/components/utils/wmtk/components/utils/json_utils.cpp b/components/utils/src/wmtk/components/utils/json_utils.cpp similarity index 100% rename from components/utils/wmtk/components/utils/json_utils.cpp rename to components/utils/src/wmtk/components/utils/json_utils.cpp diff --git a/components/utils/wmtk/components/utils/json_utils.hpp b/components/utils/src/wmtk/components/utils/json_utils.hpp similarity index 100% rename from components/utils/wmtk/components/utils/json_utils.hpp rename to components/utils/src/wmtk/components/utils/json_utils.hpp diff --git a/components/utils/wmtk/components/utils/resolve_path.cpp b/components/utils/src/wmtk/components/utils/resolve_path.cpp similarity index 100% rename from components/utils/wmtk/components/utils/resolve_path.cpp rename to components/utils/src/wmtk/components/utils/resolve_path.cpp diff --git a/components/utils/wmtk/components/utils/resolve_path.hpp b/components/utils/src/wmtk/components/utils/resolve_path.hpp similarity index 100% rename from components/utils/wmtk/components/utils/resolve_path.hpp rename to components/utils/src/wmtk/components/utils/resolve_path.hpp diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing2d.cpp index d58c937bdf..9d46318801 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing2d.cpp @@ -85,7 +85,10 @@ using namespace invariants; std::vector, std::string>> wildmeshing2d( const WildMeshingOptions& options) { - auto mesh = options.input_mesh; + auto mesh = std::dynamic_pointer_cast(options.input_mesh); + if(!bool(mesh)) { + throw std::runtime_error("input mesh of wildmeshing3d must be a trimesh"); + } if (!mesh->is_connectivity_valid()) { throw std::runtime_error("input mesh for wildmeshing connectivity invalid"); diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing3d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing3d.cpp index 89096a93f6..b87b24567f 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing3d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing3d.cpp @@ -88,7 +88,10 @@ using namespace invariants; std::vector, std::string>> wildmeshing3d( const WildMeshingOptions& options) { - auto mesh = options.input_mesh; + auto mesh = std::dynamic_pointer_cast(options.input_mesh); + if(!bool(mesh)) { + throw std::runtime_error("input mesh of wildmeshing3d must be a tetmesh"); + } if (!mesh->is_connectivity_valid()) { throw std::runtime_error("input mesh for wildmeshing connectivity invalid"); @@ -1775,4 +1778,4 @@ void set_operation_energy_filter_after_sizing_field( } -} // namespace wmtk::components::internal \ No newline at end of file +} // namespace wmtk::components::internal diff --git a/src/wmtk/Mesh.cpp b/src/wmtk/Mesh.cpp index 06cfe8a746..e1a1cd2315 100644 --- a/src/wmtk/Mesh.cpp +++ b/src/wmtk/Mesh.cpp @@ -38,7 +38,7 @@ simplex::IdSimplex Mesh::get_id_simplex(const simplex::Simplex& s) const simplex::Simplex Mesh::get_simplex(const simplex::IdSimplex& s) const { const Tuple& t = tuple_from_id(s.primitive_type(), s.index()); - return simplex::Simplex(*this, s.primitive_type(), t); + return simplex::Simplex(s.primitive_type(), t); } Tuple Mesh::get_tuple_from_id_simplex(const simplex::IdSimplex& s) const @@ -112,7 +112,16 @@ bool Mesh::is_boundary(const simplex::Simplex& s) const bool Mesh::is_valid(const Tuple& tuple) const { - return !tuple.is_null() && !is_removed(tuple); + const bool nullity = tuple.is_null(); + const bool removed = is_removed(tuple); + const bool bad = nullity || removed; +#if !defined(NDEBUG) + if(bad) { + logger().debug("Mesh::is_valid failed, got nullity:{} removedness:{}", nullity, removed); + + } +#endif + return !bad; } bool Mesh::is_removed(const Tuple& tuple) const @@ -143,16 +152,7 @@ bool Mesh::is_removed(int64_t index, PrimitiveType pt) const bool Mesh::is_valid(const simplex::Simplex& s) const { -#if defined(WMTK_ENABLE_SIMPLEX_ID_CACHING) - if (!is_valid(s.tuple())) { - return false; - } else { - const int64_t id_tuple = id(s.tuple(), s.primitive_type()); - return id_tuple == s.m_index; - } -#else return is_valid(s.tuple()) && !is_removed(s.tuple(), s.primitive_type()); -#endif } diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index e850800167..0e554639a7 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -29,6 +29,7 @@ #include "multimesh/attribute/UseParentScopeRAII.hpp" + #include "simplex/IdSimplex.hpp" #include "simplex/NavigatableSimplex.hpp" #include "simplex/Simplex.hpp" diff --git a/src/wmtk/TriMesh.hpp b/src/wmtk/TriMesh.hpp index 57aa8d5730..532e929674 100644 --- a/src/wmtk/TriMesh.hpp +++ b/src/wmtk/TriMesh.hpp @@ -54,6 +54,7 @@ class TriMesh : public MeshCRTP void initialize(Eigen::Ref F, bool make_free = false); void initialize_free(int64_t count); + using Mesh::is_valid; bool is_valid(const Tuple& tuple) const final override; bool is_connectivity_valid() const final override; diff --git a/src/wmtk/TriMeshOperationExecutor.cpp b/src/wmtk/TriMeshOperationExecutor.cpp index 496d5bc74f..3ba91fa97a 100644 --- a/src/wmtk/TriMeshOperationExecutor.cpp +++ b/src/wmtk/TriMeshOperationExecutor.cpp @@ -705,6 +705,7 @@ void TriMesh::TriMeshOperationExecutor::collapse_edge() const int64_t& v0 = m_spine_vids[0]; const int64_t& v1 = m_spine_vids[1]; + // replace v0 by v1 in incident faces for (const int64_t fid : v0_neighbors) { bool is_fid_deleted = false; @@ -754,6 +755,9 @@ void TriMesh::TriMeshOperationExecutor::collapse_edge() delete_simplices(); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(m_mesh.is_connectivity_valid()); +#endif // return a ccw tuple from left ear if it exists, otherwise return a ccw tuple from right ear // return m_mesh.tuple_from_id(PrimitiveType::Vertex, v1); diff --git a/src/wmtk/Types.hpp b/src/wmtk/Types.hpp index bf7255c3f2..4c09b10dd4 100644 --- a/src/wmtk/Types.hpp +++ b/src/wmtk/Types.hpp @@ -18,6 +18,8 @@ using Vector = Eigen::Matrix; template using VectorX = Vector; +template +using Vector1 = Vector; template using Vector2 = Vector; template 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; diff --git a/src/wmtk/autogen/tri_mesh/is_ccw.hxx b/src/wmtk/autogen/tri_mesh/is_ccw.hxx index 364c2a2574..d1e42cf3e0 100644 --- a/src/wmtk/autogen/tri_mesh/is_ccw.hxx +++ b/src/wmtk/autogen/tri_mesh/is_ccw.hxx @@ -7,13 +7,6 @@ #include "local_id_table_offset.hpp" namespace wmtk::autogen::tri_mesh { -inline bool is_ccw(const Tuple& tuple) -{ - assert(tuple_is_valid_for_ccw(tuple)); - using namespace utils; - const int64_t offset = local_id_table_offset(tuple); - return auto_2d_table_ccw[offset] == 1; -} inline bool tuple_is_valid_for_ccw(const Tuple& tuple) { using namespace utils; @@ -23,4 +16,11 @@ inline bool tuple_is_valid_for_ccw(const Tuple& tuple) const int64_t offset = local_id_table_offset(tuple); return auto_2d_table_ccw[offset] != -1; } +inline bool is_ccw(const Tuple& tuple) +{ + assert(tuple_is_valid_for_ccw(tuple)); + using namespace utils; + const int64_t offset = local_id_table_offset(tuple); + return auto_2d_table_ccw[offset] == 1; +} } // namespace wmtk::autogen::tri_mesh diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index 98a0ea9ce9..bcbb267993 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -164,6 +164,11 @@ EnvelopeInvariant::EnvelopeInvariant( } else { throw std::runtime_error("Envelope mesh handle type invlid"); } + assert(has_envelope()); +} +bool EnvelopeInvariant::has_envelope() const +{ + return bool(m_bvh) || bool(m_envelope); } bool EnvelopeInvariant::after( diff --git a/src/wmtk/invariants/EnvelopeInvariant.hpp b/src/wmtk/invariants/EnvelopeInvariant.hpp index ea9f6b4fcf..3880276a7d 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.hpp +++ b/src/wmtk/invariants/EnvelopeInvariant.hpp @@ -28,7 +28,12 @@ class EnvelopeInvariant : public Invariant const std::vector& top_dimension_tuples_before, const std::vector& top_dimension_tuples_after) const override; + const std::shared_ptr& bvh() const { return m_bvh; } + const attribute::MeshAttributeHandle& coordinate_handle() const { return m_coordinate_handle; } + + private: + bool has_envelope() const; std::shared_ptr m_envelope = nullptr; std::shared_ptr m_bvh = nullptr; const attribute::MeshAttributeHandle m_coordinate_handle; diff --git a/src/wmtk/invariants/MultiMeshMapValidInvariant.cpp b/src/wmtk/invariants/MultiMeshMapValidInvariant.cpp index daecfc44c0..13692883e6 100644 --- a/src/wmtk/invariants/MultiMeshMapValidInvariant.cpp +++ b/src/wmtk/invariants/MultiMeshMapValidInvariant.cpp @@ -34,13 +34,12 @@ bool both_map_to_child(const Mesh& parent, const Mesh& child, const Tuple& input assert(parent_type > child_type); const PrimitiveType collapsed_simplex_type = std::min(child_type + 1, parent_type); auto opposite = [&parent, collapsed_simplex_type](Tuple t) { - //switch(collapsed_simplex_type) { - // case PrimitiveType::Tetrahedron: - // t = parent.switch_tuples(t, {PrimitiveType::Vertex, PrimitiveType::Edge, PrimitiveType::Triangle}); - // case PrimitiveType::Triangle: - // t = parent.switch_tuples(t, {PrimitiveType::Vertex, PrimitiveType::Edge}); - // case PrimitiveType::Edge: - // t = parent.switch_tuple(t, PrimitiveType::Vertex); + // switch(collapsed_simplex_type) { + // case PrimitiveType::Tetrahedron: + // t = parent.switch_tuples(t, {PrimitiveType::Vertex, PrimitiveType::Edge, + // PrimitiveType::Triangle}); case PrimitiveType::Triangle: t = parent.switch_tuples(t, + // {PrimitiveType::Vertex, PrimitiveType::Edge}); case PrimitiveType::Edge: t = + // parent.switch_tuple(t, PrimitiveType::Vertex); // default: // case PrimitiveType::Vertex: @@ -71,13 +70,14 @@ bool any_pairs_both_map_to_child( assert(edge.primitive_type() == PrimitiveType::Edge); const PrimitiveType parent_type = parent.top_simplex_type(); const PrimitiveType child_type = child.top_simplex_type(); - assert(parent_type > child_type); if (parent_type == child_type) { // if the meshes are the same dimension then there isn't a pair, so this function returns // false return false; } else if (parent_type == child_type + 1) { return both_map_to_child(parent, child, edge.tuple()); + } else { + assert(parent_type > child_type); } for (const Tuple& tuple : simplex::cofaces_single_dimension_iterable(parent, edge, child.top_simplex_type() + 1)) { diff --git a/src/wmtk/invariants/SimplexInversionInvariant.cpp b/src/wmtk/invariants/SimplexInversionInvariant.cpp index 3a5ab2a187..abb6e653f7 100644 --- a/src/wmtk/invariants/SimplexInversionInvariant.cpp +++ b/src/wmtk/invariants/SimplexInversionInvariant.cpp @@ -13,9 +13,11 @@ namespace wmtk { template SimplexInversionInvariant::SimplexInversionInvariant( const Mesh& m, - const TypedAttributeHandle& coordinate) + const TypedAttributeHandle& coordinate, + bool inverted) : Invariant(m, true, false, true) , m_coordinate_handle(coordinate) + , m_inverted(inverted) {} template @@ -42,21 +44,6 @@ bool SimplexInversionInvariant::after( utils::wmtk_orient3d(p0, p1, p2, p3)); return false; } - - // const Eigen::Vector3 p0 = accessor.const_vector_attribute(t); - // const Eigen::Vector3 p1 = - // accessor.const_vector_attribute(mymesh.switch_tuple(t, PrimitiveType::Vertex)); - // const Eigen::Vector3 p2 = accessor.const_vector_attribute( - // mymesh.switch_tuples(t, {PrimitiveType::Edge, PrimitiveType::Vertex})); - // const Eigen::Vector3 p3 = accessor.const_vector_attribute(mymesh.switch_tuples( - // t, - // {PrimitiveType::Triangle, PrimitiveType::Edge, PrimitiveType::Vertex})); - - // if (mymesh.is_ccw(t)) { - // if (utils::wmtk_orient3d(p3, p0, p1, p2) <= 0) return false; - // } else { - // if (utils::wmtk_orient3d(p3, p0, p2, p1) <= 0) return false; - // } } return true; @@ -77,7 +64,7 @@ bool SimplexInversionInvariant::after( const Eigen::Vector2 p2 = accessor.const_vector_attribute( mymesh.switch_tuples(ccw_tuple, {PrimitiveType::Edge, PrimitiveType::Vertex})); - if (utils::wmtk_orient2d(p0, p1, p2) <= 0) return false; + if (!is_oriented(p0, p1, p2)) return false; } return true; @@ -91,7 +78,7 @@ bool SimplexInversionInvariant::after( T p1 = accessor.const_scalar_attribute(mymesh.switch_tuple(tuple, PrimitiveType::Vertex)); - if (utils::wmtk_orient1d(p0, 01) >= 0) return false; + if (!is_oriented(p0, p1)) return false; } return true; @@ -100,6 +87,54 @@ bool SimplexInversionInvariant::after( return true; } +template +bool SimplexInversionInvariant::is_oriented( + const Eigen::Ref>& p0, + const Eigen::Ref>& p1) const +{ + return is_oriented(p0.x(), p1.x()); +} +template +bool SimplexInversionInvariant::is_oriented(const T& p0, const T& p1) const +{ + // + const int orient = utils::wmtk_orient1d(p0, p1); + if (m_inverted) { + return orient < 0; + } else { + return orient > 0; + } +} +template +bool SimplexInversionInvariant::is_oriented( + const Eigen::Ref>& p0, + const Eigen::Ref>& p1, + const Eigen::Ref>& p2) const +{ + // + const int orient = utils::wmtk_orient2d(p0, p1, p2); + if (m_inverted) { + return orient < 0; + } else { + return orient > 0; + } +} +template +bool SimplexInversionInvariant::is_oriented( + const Eigen::Ref>& p0, + const Eigen::Ref>& p1, + const Eigen::Ref>& p2, + const Eigen::Ref>& p3) const +{ + const int orient = utils::wmtk_orient3d(p0, p1, p2, p3); + if (m_inverted) { + return orient < 0; + } else { + return orient > 0; + } + // +} + template class SimplexInversionInvariant; template class SimplexInversionInvariant; diff --git a/src/wmtk/invariants/SimplexInversionInvariant.hpp b/src/wmtk/invariants/SimplexInversionInvariant.hpp index e233b72d12..8e0ac9e36a 100644 --- a/src/wmtk/invariants/SimplexInversionInvariant.hpp +++ b/src/wmtk/invariants/SimplexInversionInvariant.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include "Invariant.hpp" @@ -8,7 +10,10 @@ template class SimplexInversionInvariant : public Invariant { public: - SimplexInversionInvariant(const Mesh& m, const TypedAttributeHandle& coordinate); + SimplexInversionInvariant( + const Mesh& m, + const TypedAttributeHandle& coordinate, + bool inverted = false); using Invariant::Invariant; /** @@ -19,7 +24,21 @@ class SimplexInversionInvariant : public Invariant const override; private: + bool is_oriented(const T& p0, const T& p1) const; + bool is_oriented(const Eigen::Ref>& p0, const Eigen::Ref>& p1) + const; + bool is_oriented( + const Eigen::Ref>& p0, + const Eigen::Ref>& p1, + const Eigen::Ref>& p2) const; + bool is_oriented( + const Eigen::Ref>& p0, + const Eigen::Ref>& p1, + const Eigen::Ref>& p2, + const Eigen::Ref>& p3) const; + const TypedAttributeHandle m_coordinate_handle; + bool m_inverted = false; }; } // namespace wmtk diff --git a/src/wmtk/invariants/ValenceImprovementInvariant.cpp b/src/wmtk/invariants/ValenceImprovementInvariant.cpp index 0113f92e5f..75d36916b8 100644 --- a/src/wmtk/invariants/ValenceImprovementInvariant.cpp +++ b/src/wmtk/invariants/ValenceImprovementInvariant.cpp @@ -2,14 +2,18 @@ #include #include +#include namespace wmtk::invariants { -ValenceImprovementInvariant::ValenceImprovementInvariant(const Mesh& m) +ValenceImprovementInvariant::ValenceImprovementInvariant(const TriMesh& m) : Invariant(m, true, false, false) {} bool ValenceImprovementInvariant::before(const simplex::Simplex& simplex) const { + if(mesh().is_boundary(simplex)) { + return false; + } const Tuple& t = simplex.tuple(); assert(simplex.primitive_type() == PrimitiveType::Edge); diff --git a/src/wmtk/invariants/ValenceImprovementInvariant.hpp b/src/wmtk/invariants/ValenceImprovementInvariant.hpp index a6aae0c61b..f1fa06149d 100644 --- a/src/wmtk/invariants/ValenceImprovementInvariant.hpp +++ b/src/wmtk/invariants/ValenceImprovementInvariant.hpp @@ -3,12 +3,14 @@ #include #include "Invariant.hpp" +namespace wmtk { + class TriMesh; +} namespace wmtk::invariants { class ValenceImprovementInvariant : public Invariant { public: - ValenceImprovementInvariant(const Mesh& m); - using Invariant::Invariant; + ValenceImprovementInvariant(const TriMesh& m); bool before(const simplex::Simplex& t) const override; }; diff --git a/src/wmtk/io/MshReader.cpp b/src/wmtk/io/MshReader.cpp index acd58e0e1e..b7d736034a 100644 --- a/src/wmtk/io/MshReader.cpp +++ b/src/wmtk/io/MshReader.cpp @@ -16,6 +16,12 @@ #include "predicates.h" namespace wmtk::io { + +namespace { + +constexpr static int64_t AUTO_EMBEDDED_DIMENSION = -2; +} + MshReader::MshReader() = default; MshReader::~MshReader() = default; @@ -53,11 +59,12 @@ std::shared_ptr MshReader::read( // attributes on tets in a tetmesh std::shared_ptr MshReader::read( const std::filesystem::path& filename, - const int64_t embedded_dimension, + const std::optional& embedded_dimension, const std::vector>& extra_attributes) { m_spec = mshio::load_msh(filename.string()); - m_embedded_dimension = embedded_dimension == -1 ? get_embedded_dimension() : embedded_dimension; + m_embedded_dimension = + embedded_dimension.has_value() ? get_embedded_dimension() : embedded_dimension.value(); return generate(extra_attributes); @@ -65,10 +72,11 @@ std::shared_ptr MshReader::read( std::shared_ptr MshReader::read( const std::filesystem::path& filename, - const int64_t embedded_dimension) + const std::optional& embedded_dimension) { m_spec = mshio::load_msh(filename.string()); - m_embedded_dimension = embedded_dimension == -1 ? get_embedded_dimension() : embedded_dimension; + m_embedded_dimension = + embedded_dimension.has_value() ? get_embedded_dimension() : embedded_dimension.value(); return generate(); @@ -317,9 +325,8 @@ void MshReader::extract_element_attribute( } } -auto MshReader::generate( - const std::optional>>& extra_attributes_opt) - -> std::shared_ptr +auto MshReader::generate(const std::optional>>& + extra_attributes_opt) -> std::shared_ptr { std::shared_ptr res; switch (get_mesh_dimension()) { @@ -424,4 +431,4 @@ void MshReader::validate<3>() } } } -} // namespace wmtk::io \ No newline at end of file +} // namespace wmtk::io diff --git a/src/wmtk/io/MshReader.hpp b/src/wmtk/io/MshReader.hpp index 453162bc8e..73863df0c4 100644 --- a/src/wmtk/io/MshReader.hpp +++ b/src/wmtk/io/MshReader.hpp @@ -22,11 +22,11 @@ class MshReader const std::vector& extra_facet_attributes = {}); std::shared_ptr read( const std::filesystem::path& filename, - const int64_t embedded_dimension, + const std::optional& embedded_dimension, const std::vector>& extra_attributes); std::shared_ptr read( const std::filesystem::path& filename, - const int64_t embedded_dimension = -1); + const std::optional& embedded_dimension = {}); private: const mshio::NodeBlock* get_vertex_block(int DIM) const; @@ -90,8 +90,6 @@ class MshReader Eigen::MatrixXd V; MatrixXl S; - - static const int64_t AUTO_EMBEDDED_DIMENSION = -2; }; } // namespace wmtk::io diff --git a/src/wmtk/io/read_mesh.cpp b/src/wmtk/io/read_mesh.cpp index 6668605a8a..7b109b18e4 100644 --- a/src/wmtk/io/read_mesh.cpp +++ b/src/wmtk/io/read_mesh.cpp @@ -68,7 +68,7 @@ std::shared_ptr read_mesh( } case FileType::Msh: { MshReader reader; - return reader.read(filename, -1, retrieved_attributes); + return reader.read(filename, {}, retrieved_attributes); } default: case FileType::Auto: { diff --git a/src/wmtk/multimesh/MultiMeshManager.cpp b/src/wmtk/multimesh/MultiMeshManager.cpp index 0544671919..d8abcf0d3f 100644 --- a/src/wmtk/multimesh/MultiMeshManager.cpp +++ b/src/wmtk/multimesh/MultiMeshManager.cpp @@ -1,6 +1,8 @@ #include "MultiMeshManager.hpp" #include #include +// debug function that reads into this structure +#include "utils/check_map_valid.hpp" //#include #include #include diff --git a/src/wmtk/multimesh/MultiMeshManager.hpp b/src/wmtk/multimesh/MultiMeshManager.hpp index 9eda9624dc..e4ba6720be 100644 --- a/src/wmtk/multimesh/MultiMeshManager.hpp +++ b/src/wmtk/multimesh/MultiMeshManager.hpp @@ -7,11 +7,10 @@ #include // included to make a friend as this requires IDs #include +// just for friending later on +#include #include -// debug function that reads into this structure -#include "utils/check_map_valid.hpp" - namespace wmtk { diff --git a/src/wmtk/multimesh/utils/check_map_valid.cpp b/src/wmtk/multimesh/utils/check_map_valid.cpp index c0fe9a9a98..08b6113471 100644 --- a/src/wmtk/multimesh/utils/check_map_valid.cpp +++ b/src/wmtk/multimesh/utils/check_map_valid.cpp @@ -18,6 +18,6 @@ bool check_child_maps_valid(const Mesh& m) bool check_parent_map_valid(const Mesh& m) { MapValidator validator(m); - return validator.check_child_map_attributes_valid(); + return validator.check_parent_map_attribute_valid(); } } // namespace wmtk::multimesh::utils diff --git a/src/wmtk/operations/EdgeSplit.cpp b/src/wmtk/operations/EdgeSplit.cpp index eae7e549c6..6ea0a88a0e 100644 --- a/src/wmtk/operations/EdgeSplit.cpp +++ b/src/wmtk/operations/EdgeSplit.cpp @@ -20,7 +20,6 @@ bool EdgeSplit::attribute_new_all_configured() const for (const auto& strat : m_new_attr_strategies) { if (strat->invalid_state()) { all_configured = false; - wmtk::logger().warn("Attribute new {} was not configured", strat->name()); } } return all_configured; @@ -86,12 +85,22 @@ void EdgeSplit::set_new_attribute_strategy( const attribute::MeshAttributeHandle& attribute, const std::shared_ptr& other) { + bool done = false; for (size_t i = 0; i < m_new_attr_strategies.size(); ++i) { if (m_new_attr_strategies[i]->matches_attribute(attribute)) { + if(done) { + throw std::runtime_error("Two of one attr strat"); + } + auto old = m_new_attr_strategies[i]; m_new_attr_strategies[i] = other; - return; + other->invalid_state(); + done = true; + //return; } } + if(done) { + return ; + } throw std::runtime_error("unable to find attribute"); } diff --git a/src/wmtk/operations/Operation.cpp b/src/wmtk/operations/Operation.cpp index 7deb38c263..0402fc7e0c 100644 --- a/src/wmtk/operations/Operation.cpp +++ b/src/wmtk/operations/Operation.cpp @@ -65,9 +65,6 @@ void Operation::add_transfer_strategy( std::vector Operation::operator()(const simplex::Simplex& simplex) { - if (!mesh().is_valid(simplex)) { - return {}; - } if (!before(simplex)) { return {}; } @@ -104,7 +101,8 @@ bool Operation::before(const simplex::Simplex& simplex) const // return false; // } - if (mesh().is_removed(simplex.tuple()) || !mesh().is_valid(simplex)) { + // we assume the current MeshType's is_valid calls Mesh::is_valid first, which checks if the simplex is removed or not + if(!mesh().is_valid(simplex)) { return false; } @@ -152,8 +150,9 @@ void Operation::apply_attribute_transfer(const std::vector& di for (const auto& s : direct_mods) { if (!s.tuple().is_null()) { assert(m_mesh.is_valid(s)); - assert(m_mesh.get_const_flag_accessor(s.primitive_type()).is_active(s)); for (const simplex::IdSimplex& ss : simplex::closed_star_iterable(m_mesh, s)) { + // trying to get a simplex and this crashes + m_mesh.get_simplex(ss); all.add(ss); } } @@ -165,6 +164,7 @@ void Operation::apply_attribute_transfer(const std::vector& di for (const auto& at_ptr : m_attr_transfer_strategies) { if (&m_mesh == &(at_ptr->mesh())) { for (const simplex::IdSimplex& s : all.simplex_vector()) { + assert(m_mesh.get_const_flag_accessor(s.primitive_type()).is_active(s)); if (s.primitive_type() == at_ptr->primitive_type()) { at_ptr->run(m_mesh.get_simplex(s)); } diff --git a/src/wmtk/operations/composite/CMakeLists.txt b/src/wmtk/operations/composite/CMakeLists.txt index e607ec831d..18d97a25c9 100644 --- a/src/wmtk/operations/composite/CMakeLists.txt +++ b/src/wmtk/operations/composite/CMakeLists.txt @@ -1,4 +1,8 @@ set(SRC_FILES + + EdgeSwap.cpp + EdgeSwap.hpp + TriFaceSplit.hpp TriFaceSplit.cpp diff --git a/src/wmtk/operations/composite/EdgeSwap.cpp b/src/wmtk/operations/composite/EdgeSwap.cpp new file mode 100644 index 0000000000..534630fde0 --- /dev/null +++ b/src/wmtk/operations/composite/EdgeSwap.cpp @@ -0,0 +1,15 @@ +#include "EdgeSwap.hpp" + +namespace wmtk::operations::composite { +EdgeSwap::EdgeSwap(Mesh& m) + : EdgeSwap(m, std::make_shared(m), std::make_shared(m)) +{} +EdgeSwap::EdgeSwap( + Mesh& m, + std::shared_ptr split, + std::shared_ptr collapse) + : Operation(m) + , m_split(std::move(split)) + , m_collapse(std::move(collapse)) +{} +} // namespace wmtk::operations::composite diff --git a/src/wmtk/operations/composite/EdgeSwap.hpp b/src/wmtk/operations/composite/EdgeSwap.hpp new file mode 100644 index 0000000000..bb1843477e --- /dev/null +++ b/src/wmtk/operations/composite/EdgeSwap.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include + +namespace wmtk::operations::composite { +class EdgeSwap : public Operation +{ +public: + EdgeSwap(Mesh& m); + EdgeSwap(Mesh& m, std::shared_ptr split, std::shared_ptr collapse); + + EdgeSplit& split() { return *m_split; } + EdgeCollapse& collapse() { return *m_collapse; } + const EdgeSplit& split() const { return *m_split; } + const EdgeCollapse& collapse() const { return *m_collapse; } + +protected: + std::shared_ptr m_split; + std::shared_ptr m_collapse; +}; +} // namespace wmtk::operations::composite + diff --git a/src/wmtk/operations/composite/ProjectOperation.cpp b/src/wmtk/operations/composite/ProjectOperation.cpp index daf81341fe..ad87003938 100644 --- a/src/wmtk/operations/composite/ProjectOperation.cpp +++ b/src/wmtk/operations/composite/ProjectOperation.cpp @@ -22,66 +22,87 @@ ProjectOperation::ProjectOperation( , m_main_op(main_op) { for (auto& pair : mesh_constaint_pairs) { - int64_t count = 0; - int64_t index = 0; - - const std::vector& facest = - pair.first.mesh().get_all(pair.first.mesh().top_simplex_type()); - - const int64_t dim = int64_t(pair.first.mesh().top_simplex_type()) + 1; - - Eigen::MatrixXd vertices(dim * facest.size(), pair.first.dimension()); - Eigen::MatrixXi faces(facest.size(), dim); + add_constraint(pair.second, pair.first); + } +} - // hugly copy paste - if (pair.first.holds()) { - const attribute::Accessor accessor = - pair.first.mesh().create_const_accessor(pair.first.as()); - - for (const auto& f : facest) { - auto tmp = faces_single_dimension_tuples( - pair.first.mesh(), - simplex::Simplex(pair.first.mesh(), pair.first.mesh().top_simplex_type(), f), - PrimitiveType::Vertex); - - assert(tmp.size() == dim); - for (int64_t j = 0; j < tmp.size(); ++j) { - auto p = accessor.const_vector_attribute(tmp[j]); - faces(index, j) = count; - vertices.row(dim * index + j) = p; - - ++count; - } - ++index; +void ProjectOperation::add_constraint( + const attribute::MeshAttributeHandle& mah, + const attribute::MeshAttributeHandle& projection_mah) +{ + int64_t count = 0; + int64_t index = 0; + + const std::vector& facest = + projection_mah.mesh().get_all(projection_mah.mesh().top_simplex_type()); + + const int64_t dim = int64_t(projection_mah.mesh().top_simplex_type()) + 1; + + Eigen::MatrixXd vertices(dim * facest.size(), projection_mah.dimension()); + Eigen::MatrixXi faces(facest.size(), dim); + + // hugly copy paste + if (projection_mah.holds()) { + const attribute::Accessor accessor = + projection_mah.mesh().create_const_accessor(projection_mah.as()); + + for (const auto& f : facest) { + auto tmp = faces_single_dimension_tuples( + projection_mah.mesh(), + simplex::Simplex( + projection_mah.mesh(), + projection_mah.mesh().top_simplex_type(), + f), + PrimitiveType::Vertex); + + assert(tmp.size() == dim); + for (int64_t j = 0; j < tmp.size(); ++j) { + auto p = accessor.const_vector_attribute(tmp[j]); + faces(index, j) = count; + vertices.row(dim * index + j) = p; + + ++count; } - } else { - const attribute::Accessor accessor = - pair.first.mesh().create_const_accessor(pair.first.as()); - - - for (const auto& f : facest) { - auto tmp = faces_single_dimension_tuples( - pair.first.mesh(), - simplex::Simplex(pair.first.mesh(), pair.first.mesh().top_simplex_type(), f), - PrimitiveType::Vertex); - - assert(tmp.size() == dim); - for (int64_t j = 0; j < tmp.size(); ++j) { - auto p = accessor.const_vector_attribute(tmp[j]).cast(); - faces(index, j) = count; - vertices.row(dim * index + j) = p; - - ++count; - } - ++index; + ++index; + } + } else { + const attribute::Accessor accessor = + projection_mah.mesh().create_const_accessor(projection_mah.as()); + + + for (const auto& f : facest) { + auto tmp = faces_single_dimension_tuples( + projection_mah.mesh(), + simplex::Simplex( + projection_mah.mesh(), + projection_mah.mesh().top_simplex_type(), + f), + PrimitiveType::Vertex); + + assert(tmp.size() == dim); + for (int64_t j = 0; j < tmp.size(); ++j) { + auto p = accessor.const_vector_attribute(tmp[j]).cast(); + faces(index, j) = count; + vertices.row(dim * index + j) = p; + + ++count; } + ++index; } + } - auto bvh = std::make_shared(); - bvh->init(vertices, faces, 1e-10); + auto bvh = std::make_shared(); + bvh->init(vertices, faces, 1e-10); + m_bvh.emplace_back(mah, bvh); +} - m_bvh.emplace_back(pair.second, bvh); - } +void ProjectOperation::add_constraint( + const attribute::MeshAttributeHandle& mah, + std::shared_ptr bvh) +{ + assert(mah.is_valid()); + assert(bool(bvh)); + m_bvh.emplace_back(mah, bvh); } std::vector ProjectOperation::execute(const simplex::Simplex& simplex) diff --git a/src/wmtk/operations/composite/ProjectOperation.hpp b/src/wmtk/operations/composite/ProjectOperation.hpp index b6df673d38..a37e53b367 100644 --- a/src/wmtk/operations/composite/ProjectOperation.hpp +++ b/src/wmtk/operations/composite/ProjectOperation.hpp @@ -22,12 +22,20 @@ class ProjectOperation : public AttributesUpdate ProjectOperation( std::shared_ptr main_op, - const std::vector& mesh_constaint_pairs); + const std::vector& mesh_constaint_pairs = {}); std::vector execute(const simplex::Simplex& simplex) override; PrimitiveType primitive_type() const override { return m_main_op->primitive_type(); } + void add_constraint( + const attribute::MeshAttributeHandle& mah, + std::shared_ptr bvh); + + void add_constraint( + const attribute::MeshAttributeHandle& mah, + const attribute::MeshAttributeHandle& bvh_mesh_attribute); + private: using BVHConstrainPair = std::pair>; diff --git a/src/wmtk/operations/composite/TetCellSplit.cpp b/src/wmtk/operations/composite/TetCellSplit.cpp index 8a516f08da..177b051d38 100644 --- a/src/wmtk/operations/composite/TetCellSplit.cpp +++ b/src/wmtk/operations/composite/TetCellSplit.cpp @@ -5,7 +5,7 @@ namespace wmtk::operations::composite { -TetCellSplit::TetCellSplit(Mesh& m) +TetCellSplit::TetCellSplit(TetMesh& m) : Operation(m) , m_split(m) , m_collapse(m) diff --git a/src/wmtk/operations/composite/TetCellSplit.hpp b/src/wmtk/operations/composite/TetCellSplit.hpp index 26a2bc3698..c39138dc47 100644 --- a/src/wmtk/operations/composite/TetCellSplit.hpp +++ b/src/wmtk/operations/composite/TetCellSplit.hpp @@ -18,7 +18,7 @@ namespace wmtk::operations::composite { class TetCellSplit : public Operation { public: - TetCellSplit(Mesh& m); + TetCellSplit(TetMesh& m); PrimitiveType primitive_type() const override { return PrimitiveType::Tetrahedron; } diff --git a/src/wmtk/operations/composite/TetEdgeSwap.cpp b/src/wmtk/operations/composite/TetEdgeSwap.cpp index 0f9b4bfaf4..cf81f5843f 100644 --- a/src/wmtk/operations/composite/TetEdgeSwap.cpp +++ b/src/wmtk/operations/composite/TetEdgeSwap.cpp @@ -7,16 +7,14 @@ #include namespace wmtk::operations::composite { -TetEdgeSwap::TetEdgeSwap(Mesh& m, int64_t collapse_index) - : Operation(m) - , m_split(m) - , m_collapse(m) +TetEdgeSwap::TetEdgeSwap(TetMesh& m, int64_t collapse_index) + : EdgeSwap(m) , m_collapse_index(collapse_index) {} std::vector TetEdgeSwap::execute(const simplex::Simplex& simplex) { - const auto split_simplicies = m_split(simplex); + const auto split_simplicies = split()(simplex); if (split_simplicies.empty()) return {}; assert(split_simplicies.size() == 1); @@ -135,7 +133,7 @@ std::vector TetEdgeSwap::execute(const simplex::Simplex& simpl // do collapse const auto collapse_simplicies = - m_collapse(simplex::Simplex(mesh(), m_collapse.primitive_type(), collapse_tuple)); + collapse()(simplex::Simplex(mesh(), collapse().primitive_type(), collapse_tuple)); if (collapse_simplicies.empty()) return {}; assert(collapse_simplicies.size() == 1); diff --git a/src/wmtk/operations/composite/TetEdgeSwap.hpp b/src/wmtk/operations/composite/TetEdgeSwap.hpp index 33068bb623..892af6043d 100644 --- a/src/wmtk/operations/composite/TetEdgeSwap.hpp +++ b/src/wmtk/operations/composite/TetEdgeSwap.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include "EdgeSwap.hpp" namespace wmtk::operations::composite { @@ -15,15 +14,13 @@ namespace wmtk::operations::composite { * */ -class TetEdgeSwap : public Operation +class TetEdgeSwap : public EdgeSwap { public: - TetEdgeSwap(Mesh& m, int64_t collapse_index = 0); + TetEdgeSwap(TetMesh& m, int64_t collapse_index = 0); PrimitiveType primitive_type() const override { return PrimitiveType::Edge; } - inline EdgeSplit& split() { return m_split; } - inline EdgeCollapse& collapse() { return m_collapse; } protected: @@ -32,10 +29,8 @@ class TetEdgeSwap : public Operation std::vector execute(const simplex::Simplex& simplex) override; private: - EdgeSplit m_split; - EdgeCollapse m_collapse; int64_t m_collapse_index; }; -} // namespace wmtk::operations::composite \ No newline at end of file +} // namespace wmtk::operations::composite diff --git a/src/wmtk/operations/composite/TetFaceSwap.cpp b/src/wmtk/operations/composite/TetFaceSwap.cpp index 57fbd416c5..a34f007b4b 100644 --- a/src/wmtk/operations/composite/TetFaceSwap.cpp +++ b/src/wmtk/operations/composite/TetFaceSwap.cpp @@ -2,7 +2,7 @@ #include namespace wmtk::operations::composite { -TetFaceSwap::TetFaceSwap(Mesh& m) +TetFaceSwap::TetFaceSwap(TetMesh& m) : Operation(m) , m_split(m) , m_collapse(m) diff --git a/src/wmtk/operations/composite/TetFaceSwap.hpp b/src/wmtk/operations/composite/TetFaceSwap.hpp index c73dde1ce1..8018ea9fe8 100644 --- a/src/wmtk/operations/composite/TetFaceSwap.hpp +++ b/src/wmtk/operations/composite/TetFaceSwap.hpp @@ -37,7 +37,7 @@ namespace wmtk::operations::composite { class TetFaceSwap : public Operation { public: - TetFaceSwap(Mesh& m); + TetFaceSwap(TetMesh& m); PrimitiveType primitive_type() const override { return PrimitiveType::Triangle; } @@ -54,4 +54,4 @@ class TetFaceSwap : public Operation EdgeCollapse m_collapse; }; -} // namespace wmtk::operations::composite \ No newline at end of file +} // namespace wmtk::operations::composite diff --git a/src/wmtk/operations/composite/TriEdgeSwap.cpp b/src/wmtk/operations/composite/TriEdgeSwap.cpp index 99935fb082..8ed5706b37 100644 --- a/src/wmtk/operations/composite/TriEdgeSwap.cpp +++ b/src/wmtk/operations/composite/TriEdgeSwap.cpp @@ -4,10 +4,8 @@ namespace wmtk::operations::composite { -TriEdgeSwap::TriEdgeSwap(Mesh& m) - : Operation(m) - , m_split(m) - , m_collapse(m) +TriEdgeSwap::TriEdgeSwap(TriMesh& m) + : EdgeSwap(m) {} @@ -21,7 +19,7 @@ std::vector TriEdgeSwap::execute(const simplex::Simplex& simpl // \ / // \ / // \ / - const auto split_simplicies = m_split(simplex); + const auto split_simplicies = split()(simplex); if (split_simplicies.empty()) return {}; assert(split_simplicies.size() == 1); @@ -47,7 +45,7 @@ std::vector TriEdgeSwap::execute(const simplex::Simplex& simpl // \ | / // \|/ const auto collapse_simplicies = - m_collapse(simplex::Simplex(mesh(), m_collapse.primitive_type(), collapse_input_tuple)); + collapse()(simplex::Simplex(mesh(), collapse().primitive_type(), collapse_input_tuple)); if (collapse_simplicies.empty()) return {}; assert(collapse_simplicies.size() == 1); diff --git a/src/wmtk/operations/composite/TriEdgeSwap.hpp b/src/wmtk/operations/composite/TriEdgeSwap.hpp index 2e35b040f4..ce3608fe5b 100644 --- a/src/wmtk/operations/composite/TriEdgeSwap.hpp +++ b/src/wmtk/operations/composite/TriEdgeSwap.hpp @@ -1,8 +1,7 @@ #pragma once -#include -#include +#include "EdgeSwap.hpp" namespace wmtk::operations::composite { /** @@ -37,24 +36,19 @@ namespace wmtk::operations::composite { * X */ -class TriEdgeSwap : public Operation +class TriEdgeSwap : public EdgeSwap { public: - TriEdgeSwap(Mesh& m); + TriEdgeSwap(TriMesh& m); PrimitiveType primitive_type() const override { return PrimitiveType::Edge; } - inline EdgeSplit& split() { return m_split; } - inline EdgeCollapse& collapse() { return m_collapse; } protected: std::vector unmodified_primitives( const simplex::Simplex& simplex) const override; std::vector execute(const simplex::Simplex& simplex) override; -private: - EdgeSplit m_split; - EdgeCollapse m_collapse; }; } // namespace wmtk::operations::composite diff --git a/src/wmtk/operations/composite/TriFaceSplit.cpp b/src/wmtk/operations/composite/TriFaceSplit.cpp index 84299ea858..a30dae0bee 100644 --- a/src/wmtk/operations/composite/TriFaceSplit.cpp +++ b/src/wmtk/operations/composite/TriFaceSplit.cpp @@ -4,7 +4,7 @@ namespace wmtk::operations::composite { -TriFaceSplit::TriFaceSplit(Mesh& m) +TriFaceSplit::TriFaceSplit(TriMesh& m) : Operation(m) , m_split(m) , m_collapse(m) diff --git a/src/wmtk/operations/composite/TriFaceSplit.hpp b/src/wmtk/operations/composite/TriFaceSplit.hpp index b319c342c0..49b905e07c 100644 --- a/src/wmtk/operations/composite/TriFaceSplit.hpp +++ b/src/wmtk/operations/composite/TriFaceSplit.hpp @@ -20,7 +20,7 @@ namespace wmtk::operations::composite { class TriFaceSplit : public Operation { public: - TriFaceSplit(Mesh& m); + TriFaceSplit(TriMesh& m); PrimitiveType primitive_type() const override { return PrimitiveType::Triangle; } diff --git a/src/wmtk/operations/utils/MultiMeshEdgeCollapseFunctor.cpp b/src/wmtk/operations/utils/MultiMeshEdgeCollapseFunctor.cpp index dcf71f7047..d53ecd87aa 100644 --- a/src/wmtk/operations/utils/MultiMeshEdgeCollapseFunctor.cpp +++ b/src/wmtk/operations/utils/MultiMeshEdgeCollapseFunctor.cpp @@ -24,8 +24,15 @@ tri_mesh::EdgeOperationData MultiMeshEdgeCollapseFunctor::operator()( TriMesh& m, const simplex::Simplex& s) const { + assert(m.is_valid(s)); TriMesh::TriMeshOperationExecutor exec(m, s.tuple()); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(m.is_connectivity_valid()); +#endif exec.collapse_edge(); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(m.is_connectivity_valid()); +#endif return std::move(static_cast(exec)); } tet_mesh::EdgeOperationData MultiMeshEdgeCollapseFunctor::operator()( diff --git a/src/wmtk/operations/utils/multi_mesh_edge_collapse.cpp b/src/wmtk/operations/utils/multi_mesh_edge_collapse.cpp index 20fe1349e7..e34ca9188a 100644 --- a/src/wmtk/operations/utils/multi_mesh_edge_collapse.cpp +++ b/src/wmtk/operations/utils/multi_mesh_edge_collapse.cpp @@ -30,10 +30,19 @@ CollapseReturnData multi_mesh_edge_collapse( multimesh::MultiMeshSimplexVisitor visitor( std::integral_constant{}, // specify that this runs over edges MultiMeshEdgeCollapseFunctor{}); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(mesh.is_connectivity_valid()); +#endif visitor.execute_from_root(mesh, t); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(mesh.is_connectivity_valid()); +#endif multimesh::MultiMeshSimplexEventVisitor event_visitor(visitor); event_visitor.run_on_nodes(UpdateEdgeOperationMultiMeshMapFunctor{}); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(mesh.is_connectivity_valid()); +#endif auto cache = visitor.take_cache(); @@ -49,8 +58,14 @@ CollapseReturnData multi_mesh_edge_collapse( } }; +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(mesh.is_connectivity_valid()); +#endif multimesh::MultiMeshVisitor(update_attributes).execute_from_root(mesh); +#if defined(MTAO_CONSTANTLY_VERIFY_MESH) + assert(mesh.is_connectivity_valid()); +#endif return cache; } std::vector multi_mesh_edge_collapse_with_modified_simplices( 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; } diff --git a/src/wmtk/simplex/Simplex.hpp b/src/wmtk/simplex/Simplex.hpp index 75d1055cc4..26df829891 100644 --- a/src/wmtk/simplex/Simplex.hpp +++ b/src/wmtk/simplex/Simplex.hpp @@ -77,9 +77,5 @@ class Simplex // these operations are only internally defined if caching is enabled to make sure there's a // consistent semantic when simplex id caching is enabled vs not -#if defined(WMTK_ENABLE_SIMPLEX_ID_CACHING) - bool operator==(const Simplex& o) const; - bool operator<(const Simplex& o) const; -#endif }; } // namespace wmtk::simplex diff --git a/src/wmtk/utils/CMakeLists.txt b/src/wmtk/utils/CMakeLists.txt index 046c41ee4b..af35a179a9 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,12 @@ set(SRC_FILES DynamicArray.hpp DynamicArray.hxx + + 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/DynamicArray.hpp b/src/wmtk/utils/DynamicArray.hpp index 58e530ca9d..16ec46c8e4 100644 --- a/src/wmtk/utils/DynamicArray.hpp +++ b/src/wmtk/utils/DynamicArray.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace wmtk::utils { @@ -13,9 +14,11 @@ class DynamicArray class Iterator { public: + using value_type = T; Iterator(const DynamicArray* container, const uint64_t index = 0); Iterator operator++(); bool operator!=(const Iterator& other) const; + bool operator==(const Iterator& other) const; T operator*(); private: @@ -23,6 +26,10 @@ class DynamicArray uint64_t m_index = 0; }; + using value_type = T; + using iterator_type = Iterator; + using const_iterator_type = Iterator; + T& operator[](const uint64_t index); const T& operator[](const uint64_t index) const; @@ -59,4 +66,4 @@ class DynamicArray } // namespace wmtk::utils -#include "DynamicArray.hxx" \ No newline at end of file +#include "DynamicArray.hxx" diff --git a/src/wmtk/utils/DynamicArray.hxx b/src/wmtk/utils/DynamicArray.hxx index 979d9015ba..16895da909 100644 --- a/src/wmtk/utils/DynamicArray.hxx +++ b/src/wmtk/utils/DynamicArray.hxx @@ -115,6 +115,12 @@ typename DynamicArray::Iterator DynamicArray::Iterat return *this; } +template +bool DynamicArray::Iterator::operator==(const Iterator& other) const +{ + return m_index == other.m_index; +} + template bool DynamicArray::Iterator::operator!=(const Iterator& other) const { @@ -127,4 +133,4 @@ T DynamicArray::Iterator::operator*() return (*m_container)[m_index]; } -} // namespace wmtk::utils \ No newline at end of file +} // 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/TupleInspector.cpp b/src/wmtk/utils/TupleInspector.cpp index 6d7aaaf8ba..a86a5962e3 100644 --- a/src/wmtk/utils/TupleInspector.cpp +++ b/src/wmtk/utils/TupleInspector.cpp @@ -1,5 +1,6 @@ #include "TupleInspector.hpp" +#include #include namespace wmtk::utils { diff --git a/src/wmtk/utils/edgemesh_topology_initialization.h b/src/wmtk/utils/edgemesh_topology_initialization.h index 55f85e9b58..9f2ba51606 100644 --- a/src/wmtk/utils/edgemesh_topology_initialization.h +++ b/src/wmtk/utils/edgemesh_topology_initialization.h @@ -7,6 +7,6 @@ namespace wmtk { std::tuple edgemesh_topology_initialization( - Eigen::Ref E); // returns {EE, VE} + Eigen::Ref EV); // returns {EE, VE} -} \ No newline at end of file +} 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/tetmesh_topology_initialization.h b/src/wmtk/utils/tetmesh_topology_initialization.h index dbfaebd509..10f3693178 100644 --- a/src/wmtk/utils/tetmesh_topology_initialization.h +++ b/src/wmtk/utils/tetmesh_topology_initialization.h @@ -12,6 +12,6 @@ namespace wmtk { */ std::tuple -tetmesh_topology_initialization(Eigen::Ref T); +tetmesh_topology_initialization(Eigen::Ref TV); // returns TE, TF, TT, VT, ET, FT -} // namespace wmtk \ No newline at end of file +} // namespace wmtk diff --git a/src/wmtk/utils/trimesh_topology_initialization.h b/src/wmtk/utils/trimesh_topology_initialization.h index 7cdac86aac..05ec0949e1 100644 --- a/src/wmtk/utils/trimesh_topology_initialization.h +++ b/src/wmtk/utils/trimesh_topology_initialization.h @@ -7,6 +7,6 @@ namespace wmtk { std::tuple trimesh_topology_initialization( - Eigen::Ref F); // returns {FE, FF, VF, EF} + Eigen::Ref FV); // returns {FE, FF, VF, EF} -} \ No newline at end of file +} 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/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..09f5f3caee --- /dev/null +++ b/tests/attributes/attribute_type.cpp @@ -0,0 +1,19 @@ +#include + +#include +#include + + +using namespace wmtk::attribute; + +TEST_CASE("test_attribute_type_names", "[attributes]") +{ + using AT = AttributeType; + // 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"); + +} + diff --git a/tests/io/test_io.cpp b/tests/io/test_io.cpp index c1dae7af7f..3c4195fd11 100644 --- a/tests/io/test_io.cpp +++ b/tests/io/test_io.cpp @@ -95,8 +95,9 @@ TEST_CASE("paraview_2d", "[io]") TEST_CASE("paraview_2d_vtag", "[io]") { auto mesh = read_mesh(WMTK_DATA_DIR "/fan.msh"); - mesh->register_attribute("tag", PrimitiveType::Triangle, 1); - mesh->register_attribute("tag1", PrimitiveType::Vertex, 1); + // a,b are not used, but prevents a nodiscard warning + auto a = mesh->register_attribute("tag", PrimitiveType::Triangle, 1); + auto b = mesh->register_attribute("tag1", PrimitiveType::Vertex, 1); ParaviewWriter writer("paraview_2d_vtag", "vertices", *mesh, true, true, true, false); CHECK_NOTHROW(mesh->serialize(writer)); 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)); + } +}