diff --git a/src/Domain/Creators/CMakeLists.txt b/src/Domain/Creators/CMakeLists.txt index 4369d4e2bcc4..3cce3e93b839 100644 --- a/src/Domain/Creators/CMakeLists.txt +++ b/src/Domain/Creators/CMakeLists.txt @@ -10,7 +10,6 @@ spectre_target_sources( PRIVATE AlignedLattice.cpp BinaryCompactObject.cpp - BlockGroups.cpp Cylinder.cpp CylindricalBinaryCompactObject.cpp Disk.cpp @@ -30,7 +29,6 @@ spectre_target_headers( HEADERS AlignedLattice.hpp BinaryCompactObject.hpp - BlockGroups.hpp Cylinder.hpp CylindricalBinaryCompactObject.hpp Disk.hpp diff --git a/src/Domain/FunctionsOfTime/CMakeLists.txt b/src/Domain/FunctionsOfTime/CMakeLists.txt index 901a17a50044..2254f3557503 100644 --- a/src/Domain/FunctionsOfTime/CMakeLists.txt +++ b/src/Domain/FunctionsOfTime/CMakeLists.txt @@ -48,5 +48,4 @@ target_link_libraries( Utilities PRIVATE ErrorHandling - H5 ) diff --git a/src/Domain/Creators/BlockGroups.cpp b/src/Domain/Structure/BlockGroups.cpp similarity index 98% rename from src/Domain/Creators/BlockGroups.cpp rename to src/Domain/Structure/BlockGroups.cpp index 4ac352d83ad7..0ef8f1b3e1a0 100644 --- a/src/Domain/Creators/BlockGroups.cpp +++ b/src/Domain/Structure/BlockGroups.cpp @@ -1,7 +1,7 @@ // Distributed under the MIT License. // See LICENSE.txt for details. -#include "Domain/Creators/BlockGroups.hpp" +#include "Domain/Structure/BlockGroups.hpp" #include #include diff --git a/src/Domain/Creators/BlockGroups.hpp b/src/Domain/Structure/BlockGroups.hpp similarity index 100% rename from src/Domain/Creators/BlockGroups.hpp rename to src/Domain/Structure/BlockGroups.hpp diff --git a/src/Domain/Structure/CMakeLists.txt b/src/Domain/Structure/CMakeLists.txt index c673f60de2af..db48be9e2fef 100644 --- a/src/Domain/Structure/CMakeLists.txt +++ b/src/Domain/Structure/CMakeLists.txt @@ -8,6 +8,7 @@ add_spectre_library(${LIBRARY}) spectre_target_sources( ${LIBRARY} PRIVATE + BlockGroups.cpp BlockNeighbor.cpp ChildSize.cpp CreateInitialMesh.cpp @@ -30,6 +31,7 @@ spectre_target_headers( ${LIBRARY} INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/src HEADERS + BlockGroups.hpp BlockId.hpp BlockNeighbor.hpp ChildSize.hpp diff --git a/src/Elliptic/Systems/Elasticity/Actions/InitializeConstitutiveRelation.hpp b/src/Elliptic/Systems/Elasticity/Actions/InitializeConstitutiveRelation.hpp index 9516c01e33ee..285956697efb 100644 --- a/src/Elliptic/Systems/Elasticity/Actions/InitializeConstitutiveRelation.hpp +++ b/src/Elliptic/Systems/Elasticity/Actions/InitializeConstitutiveRelation.hpp @@ -15,11 +15,11 @@ #include "DataStructures/DataBox/DataBox.hpp" #include "DataStructures/DataBox/Tag.hpp" -#include "Domain/Creators/BlockGroups.hpp" #include "Domain/Creators/DomainCreator.hpp" #include "Domain/Creators/ExpandOverBlocks.hpp" #include "Domain/Creators/OptionTags.hpp" #include "Domain/Creators/Tags/Domain.hpp" +#include "Domain/Structure/BlockGroups.hpp" #include "Domain/Structure/ElementId.hpp" #include "Domain/Tags.hpp" #include "Elliptic/Tags.hpp" diff --git a/src/Evolution/DgSubcell/SubcellOptions.cpp b/src/Evolution/DgSubcell/SubcellOptions.cpp index 132bcffcab97..0103ec172e61 100644 --- a/src/Evolution/DgSubcell/SubcellOptions.cpp +++ b/src/Evolution/DgSubcell/SubcellOptions.cpp @@ -12,8 +12,8 @@ #include #include -#include "Domain/Creators/BlockGroups.hpp" #include "Domain/Creators/DomainCreator.hpp" +#include "Domain/Structure/BlockGroups.hpp" #include "Options/Options.hpp" #include "Utilities/Algorithm.hpp" #include "Utilities/ErrorHandling/Error.hpp" diff --git a/src/IO/H5/CMakeLists.txt b/src/IO/H5/CMakeLists.txt index 54e017c4be17..19df687468fb 100644 --- a/src/IO/H5/CMakeLists.txt +++ b/src/IO/H5/CMakeLists.txt @@ -59,6 +59,7 @@ target_link_libraries( PUBLIC Boost::boost DataStructures + Domain DomainStructure ErrorHandling HDF5::HDF5 diff --git a/src/IO/H5/CombineH5.cpp b/src/IO/H5/CombineH5.cpp index 8dcb7fb5c081..3f20599ccd04 100644 --- a/src/IO/H5/CombineH5.cpp +++ b/src/IO/H5/CombineH5.cpp @@ -7,10 +7,13 @@ #include #include #include +#include #include #include #include "DataStructures/DataVector.hpp" +#include "Domain/Domain.hpp" +#include "Domain/Structure/BlockGroups.hpp" #include "IO/H5/AccessType.hpp" #include "IO/H5/CheckH5PropertiesMatch.hpp" #include "IO/H5/File.hpp" @@ -18,20 +21,37 @@ #include "IO/H5/TensorData.hpp" #include "IO/H5/VolumeData.hpp" #include "Parallel/Printf/Printf.hpp" +#include "Utilities/Algorithm.hpp" #include "Utilities/FileSystem.hpp" #include "Utilities/MakeString.hpp" +#include "Utilities/Serialization/Serialize.hpp" #include "Utilities/StdHelpers.hpp" namespace { // Returns all the observation_ids stored in the volume files. Assumes all // volume files have the same observation ids -std::vector get_observation_ids( +std::vector> get_observation_ids( const std::vector& file_names, const std::string& subfile_name) { const h5::H5File initial_file(file_names[0], false); const auto& initial_volume_file = initial_file.get(subfile_name); - return initial_volume_file.list_observation_ids(); + const std::vector observation_ids = + initial_volume_file.list_observation_ids(); + std::vector> observation_ids_and_values( + observation_ids.size()); + for (size_t i = 0; i < observation_ids.size(); ++i) { + observation_ids_and_values[i] = std::pair{ + observation_ids[i], + initial_volume_file.get_observation_value(observation_ids[i])}; + } + // Sort by the observation value + alg::sort(observation_ids_and_values, + [](const std::pair& id_and_value_a, + const std::pair& id_and_value_b) { + return id_and_value_a.second < id_and_value_b.second; + }); + return observation_ids_and_values; } // Returns total number of elements for an observation id across all volume data @@ -49,19 +69,89 @@ size_t get_number_of_elements(const std::vector& input_filenames, } return total_elements; } + +std::optional> get_block_numbers_to_use( + const std::string& file_name, const std::string& subfile_name, + const size_t observation_id, + const std::optional>& blocks_to_combine) { + if (not blocks_to_combine.has_value() or blocks_to_combine.value().empty()) { + return std::nullopt; + } + + const h5::H5File original_file(file_name, false); + const auto& volume_file = original_file.get(subfile_name); + + const auto dim = volume_file.get_dimension(); + auto serialized_domain = volume_file.get_domain(observation_id); + if (not serialized_domain.has_value()) { + ERROR("Could not read the domain the from file " + << file_name << " and subfile " << subfile_name + << ". This means we cannot filter based on block names. You can " + "still combine the files but will need to use all blocks."); + } + std::unordered_set block_names_to_combine{}; + std::vector block_names_in_domain{}; + switch (dim) { + case 1: { + const auto domain = + deserialize>(serialized_domain.value().data()); + block_names_in_domain = domain.block_names(); + block_names_to_combine = domain::expand_block_groups_to_block_names( + blocks_to_combine.value(), domain.block_names(), + domain.block_groups()); + break; + } + case 2: { + const auto domain = + deserialize>(serialized_domain.value().data()); + block_names_in_domain = domain.block_names(); + block_names_to_combine = domain::expand_block_groups_to_block_names( + blocks_to_combine.value(), domain.block_names(), + domain.block_groups()); + break; + } + case 3: { + const auto domain = + deserialize>(serialized_domain.value().data()); + block_names_in_domain = domain.block_names(); + block_names_to_combine = domain::expand_block_groups_to_block_names( + blocks_to_combine.value(), domain.block_names(), + domain.block_groups()); + break; + } + default: + ERROR("Only can handle 1, 2, or 3d domains not " << dim); + }; + + std::unordered_set blocks_to_use{}; + for (const std::string& block_to_combine : block_names_to_combine) { + auto location_it = alg::find(block_names_in_domain, block_to_combine); + if (location_it == block_names_in_domain.end()) { + ERROR("Block name " << block_to_combine << " not found."); + } + blocks_to_use.insert(static_cast( + std::distance(block_names_in_domain.begin(), location_it))); + } + + return blocks_to_use; +} } // namespace + namespace h5 { -void combine_h5(const std::vector& file_names, - const std::string& subfile_name, const std::string& output, - const bool check_src) { +void combine_h5( + const std::vector& file_names, const std::string& subfile_name, + const std::string& output, const std::optional start_value, + const std::optional stop_value, + const std::optional>& blocks_to_combine, + const bool check_src) { // Parses for and stores all input files to be looped over Parallel::printf("Processing files:\n%s\n", std::string{MakeString{} << file_names}.c_str()); // Checks that volume data was generated with identical versions of SpECTRE if (check_src) { - if (!h5::check_src_files_match(file_names)) { + if (not h5::check_src_files_match(file_names)) { ERROR( "One or more of your files were found to have differing src.tar.gz " "files, meaning that they may be from differing versions of " @@ -70,7 +160,7 @@ void combine_h5(const std::vector& file_names, } // Checks that volume data files contain the same observation ids - if (!h5::check_observation_ids_match(file_names, subfile_name)) { + if (not h5::check_observation_ids_match(file_names, subfile_name)) { ERROR( "One or more of your files were found to have differing observation " "ids, meaning they may be from different runs of your SpECTRE " @@ -88,12 +178,30 @@ void combine_h5(const std::vector& file_names, } // End of scope for H5 file // Obtains list of observation ids to loop over - const std::vector observation_ids = + const std::vector> observation_ids_and_values = get_observation_ids(file_names, subfile_name); + if (observation_ids_and_values.empty()) { + ERROR("No observation IDs found in subfile" << subfile_name); + } + + const std::optional> blocks_to_use = + get_block_numbers_to_use(file_names[0], subfile_name, + observation_ids_and_values[0].first, + blocks_to_combine); + // Loops over observation ids to write volume data by observation id - for (size_t obs_index = 0; obs_index < observation_ids.size(); ++obs_index) { - const size_t obs_id = observation_ids[obs_index]; + for (size_t obs_index = 0; obs_index < observation_ids_and_values.size(); + ++obs_index) { + const double obs_value = observation_ids_and_values[obs_index].second; + if (obs_value > stop_value.value_or(std::numeric_limits::max()) or + obs_value < + start_value.value_or(std::numeric_limits::lowest())) { + Parallel::printf("Skipping observation value %1.6e\n", obs_value); + continue; + } + + const size_t obs_id = observation_ids_and_values[obs_index].first; // Pre-calculates size of vector to store element data and allocates // corresponding memory const size_t vector_dim = @@ -117,11 +225,12 @@ void combine_h5(const std::vector& file_names, if (not printed) { Parallel::printf( "Processing obsevation ID %lo (%lo/%lo) with value %1.14e\n", - obs_id, obs_index, observation_ids.size(), obs_val); + obs_id, obs_index, observation_ids_and_values.size(), obs_val); printed = true; } Parallel::printf(" Processing file: %s\n", file_name.c_str()); + const auto dim = original_volume_file.get_dimension(); serialized_domain = original_volume_file.get_domain(obs_id); serialized_functions_of_time = original_volume_file.get_functions_of_time(obs_id); @@ -133,10 +242,32 @@ void combine_h5(const std::vector& file_names, obs_val * (1.0 + 4.0 * std::numeric_limits::epsilon()), std::nullopt)[0])); + auto end_it = data_by_element.end(); + + if (blocks_to_use.has_value()) { + end_it = alg::remove_if( + data_by_element, + [&blocks_to_use, &dim](const ElementVolumeData& element) -> bool { + switch (dim) { + case 1: + return not blocks_to_use->contains( + ElementId<1>{element.element_name}.block_id()); + case 2: + return not blocks_to_use->contains( + ElementId<2>{element.element_name}.block_id()); + case 3: + return not blocks_to_use->contains( + ElementId<3>{element.element_name}.block_id()); + default: + ERROR("Only can handle 1, 2, or 3d domains but got " << dim); + }; + }); + } + // Append vector to total vector of element data for this `obs_id` element_data.insert(element_data.end(), std::make_move_iterator(data_by_element.begin()), - std::make_move_iterator(data_by_element.end())); + std::make_move_iterator(end_it)); data_by_element.clear(); original_file.close_current_object(); } diff --git a/src/IO/H5/CombineH5.hpp b/src/IO/H5/CombineH5.hpp index 11fae10af51b..576b210d43c9 100644 --- a/src/IO/H5/CombineH5.hpp +++ b/src/IO/H5/CombineH5.hpp @@ -3,13 +3,24 @@ #pragma once +#include #include #include namespace h5 { - +/*! + * \brief Combine a volume subfile across different HDF5 files. + * + * The argument `blocks_to_combine` can list block names and block groups that + * should be combined. We ignore other blocks when combining the HDF5 + * files. This provides a way to filter volume data for easier visualization. + */ void combine_h5(const std::vector& file_names, const std::string& subfile_name, const std::string& output, - const bool check_src = true); + std::optional start_value = std::nullopt, + std::optional stop_value = std::nullopt, + const std::optional>& + blocks_to_combine = std::nullopt, + bool check_src = true); } // namespace h5 diff --git a/src/IO/H5/Python/CMakeLists.txt b/src/IO/H5/Python/CMakeLists.txt index 4382d0d1ab6b..63664919aa4c 100644 --- a/src/IO/H5/Python/CMakeLists.txt +++ b/src/IO/H5/Python/CMakeLists.txt @@ -42,6 +42,7 @@ spectre_python_link_libraries( ${LIBRARY} PRIVATE Boost::boost + DomainCreators DataStructures H5 pybind11::module diff --git a/src/IO/H5/Python/CombineH5.cpp b/src/IO/H5/Python/CombineH5.cpp index 0829f16a4938..3e64a9c589a4 100644 --- a/src/IO/H5/Python/CombineH5.cpp +++ b/src/IO/H5/Python/CombineH5.cpp @@ -7,14 +7,20 @@ #include #include +#include "Domain/Creators/RegisterDerivedWithCharm.hpp" +#include "Domain/Creators/TimeDependence/RegisterDerivedWithCharm.hpp" #include "IO/H5/CombineH5.hpp" namespace py = pybind11; namespace py_bindings { void bind_h5combine(py::module& m) { + domain::creators::register_derived_with_charm(); + domain::creators::time_dependence::register_derived_with_charm(); // Wrapper for combining h5 files m.def("combine_h5", &h5::combine_h5, py::arg("file_names"), - py::arg("subfile_name"), py::arg("output"), py::arg("check_src")); + py::arg("subfile_name"), py::arg("output"), py::arg("start-time"), + py::arg("stop-time"), py::arg("blocks_to_combine"), + py::arg("check_src")); } } // namespace py_bindings diff --git a/src/IO/H5/Python/CombineH5.py b/src/IO/H5/Python/CombineH5.py index 80d8b6082786..2ca2db3aa1e5 100644 --- a/src/IO/H5/Python/CombineH5.py +++ b/src/IO/H5/Python/CombineH5.py @@ -46,6 +46,32 @@ def combine_h5_command(): ), help="combined output filename", ) +@click.option( + "--start-time", + type=float, + help=( + "The earliest time at which to start visualizing. The start-time " + "value is included." + ), +) +@click.option( + "--stop-time", + type=float, + help=( + "The time at which to stop visualizing. The stop-time value is " + "not included." + ), +) +@click.option( + "--block", + "-b", + "block_or_group_names", + multiple=True, + help=( + "Name of block or block group to analyze. " + "Can be specified multiple times to plot several block(groups) at once." + ), +) @click.option( "--check-src/--no-check-src", default=True, @@ -55,7 +81,15 @@ def combine_h5_command(): " checked, False implies no src files to check." ), ) -def combine_h5_vol_command(h5files, subfile_name, output, check_src): +def combine_h5_vol_command( + h5files, + subfile_name, + output, + start_time, + stop_time, + block_or_group_names, + check_src, +): """Combines volume data spread over multiple H5 files into a single file The typical use case is to combine volume data from multiple nodes into a @@ -82,7 +116,15 @@ def combine_h5_vol_command(h5files, subfile_name, output, check_src): if not output.endswith(".h5"): output += ".h5" - spectre_h5.combine_h5(h5files, subfile_name, output, check_src) + spectre_h5.combine_h5( + h5files, + subfile_name, + output, + start_time, + stop_time, + block_or_group_names, + check_src, + ) if __name__ == "__main__": diff --git a/src/ParallelAlgorithms/Actions/FilterAction.hpp b/src/ParallelAlgorithms/Actions/FilterAction.hpp index 0821a8b2bf46..12615322a111 100644 --- a/src/ParallelAlgorithms/Actions/FilterAction.hpp +++ b/src/ParallelAlgorithms/Actions/FilterAction.hpp @@ -13,7 +13,7 @@ #include "DataStructures/DataBox/DataBox.hpp" #include "DataStructures/DataVector.hpp" #include "DataStructures/Matrix.hpp" -#include "Domain/Creators/BlockGroups.hpp" +#include "Domain/Structure/BlockGroups.hpp" #include "Domain/Tags.hpp" #include "NumericalAlgorithms/Spectral/Mesh.hpp" #include "Parallel/AlgorithmExecution.hpp" diff --git a/tests/Unit/Domain/Creators/CMakeLists.txt b/tests/Unit/Domain/Creators/CMakeLists.txt index 456426228b4f..98c16a2a2948 100644 --- a/tests/Unit/Domain/Creators/CMakeLists.txt +++ b/tests/Unit/Domain/Creators/CMakeLists.txt @@ -6,7 +6,6 @@ set(LIBRARY "Test_DomainCreators") set(LIBRARY_SOURCES Test_AlignedLattice.cpp Test_BinaryCompactObject.cpp - Test_BlockGroups.cpp Test_Brick.cpp Test_Cylinder.cpp Test_CylindricalBinaryCompactObject.cpp diff --git a/tests/Unit/Domain/Structure/CMakeLists.txt b/tests/Unit/Domain/Structure/CMakeLists.txt index 6875d96d6800..c92226c16520 100644 --- a/tests/Unit/Domain/Structure/CMakeLists.txt +++ b/tests/Unit/Domain/Structure/CMakeLists.txt @@ -4,6 +4,7 @@ set(LIBRARY "Test_DomainStructure") set(LIBRARY_SOURCES + Test_BlockGroups.cpp Test_BlockId.cpp Test_BlockNeighbor.cpp Test_ChildSize.cpp diff --git a/tests/Unit/Domain/Creators/Test_BlockGroups.cpp b/tests/Unit/Domain/Structure/Test_BlockGroups.cpp similarity index 97% rename from tests/Unit/Domain/Creators/Test_BlockGroups.cpp rename to tests/Unit/Domain/Structure/Test_BlockGroups.cpp index 7b1b58f56a76..f2f53bc8d0a6 100644 --- a/tests/Unit/Domain/Creators/Test_BlockGroups.cpp +++ b/tests/Unit/Domain/Structure/Test_BlockGroups.cpp @@ -8,7 +8,7 @@ #include #include -#include "Domain/Creators/BlockGroups.hpp" +#include "Domain/Structure/BlockGroups.hpp" namespace domain { diff --git a/tests/Unit/IO/H5/Test_CombineH5.py b/tests/Unit/IO/H5/Test_CombineH5.py index c4d1f3c283f0..6f8b6e3896dd 100644 --- a/tests/Unit/IO/H5/Test_CombineH5.py +++ b/tests/Unit/IO/H5/Test_CombineH5.py @@ -152,7 +152,15 @@ def test_combine_h5(self): # Run the combine_h5 command and check if any feature (for eg. # connectivity length has increased due to combining two files) - combine_h5(self.file_names, self.subfile_name, self.output_file, False) + combine_h5( + self.file_names, + self.subfile_name, + self.output_file, + None, + None, + None, + False, + ) h5_output = spectre_h5.H5File(file_name=self.output_file, mode="r") output_vol = h5_output.get_vol(self.subfile_name)