diff --git a/src/IO/H5/CombineH5.cpp b/src/IO/H5/CombineH5.cpp index aea657628f20..995d51cc6d67 100644 --- a/src/IO/H5/CombineH5.cpp +++ b/src/IO/H5/CombineH5.cpp @@ -46,7 +46,7 @@ size_t get_number_of_elements(const std::vector& input_filenames, } return total_elements; } -} // namespace +} namespace h5 { void combine_h5(const std::string& file_prefix, const std::string& subfile_name, diff --git a/src/IO/H5/CombineH5.hpp b/src/IO/H5/CombineH5.hpp index eb590f4041e2..ff71bddce464 100644 --- a/src/IO/H5/CombineH5.hpp +++ b/src/IO/H5/CombineH5.hpp @@ -21,6 +21,8 @@ #include "Utilities/StdHelpers.hpp" namespace h5 { + void combine_h5(const std::string& file_prefix, const std::string& subfile_name, const std::string& output); + } // namespace h5 diff --git a/src/IO/H5/Python/Bindings.cpp b/src/IO/H5/Python/Bindings.cpp index 69e604e9d290..57e205e7f68c 100644 --- a/src/IO/H5/Python/Bindings.cpp +++ b/src/IO/H5/Python/Bindings.cpp @@ -3,6 +3,7 @@ #include +#include "IO/H5/Python/CombineH5.hpp" #include "IO/H5/Python/Dat.hpp" #include "IO/H5/Python/File.hpp" #include "IO/H5/Python/TensorData.hpp" @@ -19,4 +20,5 @@ PYBIND11_MODULE(_Pybindings, m) { // NOLINT py_bindings::bind_h5dat(m); py_bindings::bind_h5vol(m); py_bindings::bind_tensordata(m); + py_bindings::bind_h5combine(m); } diff --git a/src/IO/H5/Python/CMakeLists.txt b/src/IO/H5/Python/CMakeLists.txt index 52306c1b2067..c7e8cbf12c28 100644 --- a/src/IO/H5/Python/CMakeLists.txt +++ b/src/IO/H5/Python/CMakeLists.txt @@ -15,6 +15,7 @@ spectre_python_add_module( VolumeData.cpp PYTHON_FILES __init__.py + CombineH5.py DeleteSubfiles.py ExtendConnectivityData.py ExtractDatFromH5.py diff --git a/src/IO/H5/Python/CombineH5.cpp b/src/IO/H5/Python/CombineH5.cpp new file mode 100644 index 000000000000..24ab02007c78 --- /dev/null +++ b/src/IO/H5/Python/CombineH5.cpp @@ -0,0 +1,20 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "IO/H5/Python/CombineH5.hpp" + +#include +#include +#include + +#include "IO/H5/CombineH5.hpp" + +namespace py = pybind11; + +namespace py_bindings { +void bind_h5combine(py::module& m) { + // Wrapper for combining h5 files + m.def("combine_h5", &h5::combine_h5, py::arg("file_prefix"), + py::arg("subfile_name"), py::arg("output")); +} +} // namespace py_bindings diff --git a/src/IO/H5/Python/CombineH5.hpp b/src/IO/H5/Python/CombineH5.hpp new file mode 100644 index 000000000000..26985ce2feaf --- /dev/null +++ b/src/IO/H5/Python/CombineH5.hpp @@ -0,0 +1,11 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include + +namespace py_bindings { +// NOLINTNEXTLINE(google-runtime-references) +void bind_h5combine(pybind11::module& m); +} // namespace py_bindings diff --git a/tests/Unit/IO/H5/CMakeLists.txt b/tests/Unit/IO/H5/CMakeLists.txt index 1ffb079cd7e4..c3526c11a672 100644 --- a/tests/Unit/IO/H5/CMakeLists.txt +++ b/tests/Unit/IO/H5/CMakeLists.txt @@ -34,6 +34,23 @@ target_link_libraries( Utilities ) +spectre_add_python_bindings_test( + "Unit.IO.H5.CombineH5.Python" + "Test_CombineH5.py" + "unit;IO;H5;Python" + PyH5 + ) + +if(${BUILD_PYTHON_BINDINGS}) + # Test is a bit slow because it writes a bunch of plot files to verify + # the argument handling works. + set_tests_properties( + "Unit.IO.H5.CombineH5.Python" + PROPERTIES + TIMEOUT 80 + ) +endif() + spectre_add_python_bindings_test( "Unit.IO.H5.Python" "Test_H5.py" diff --git a/tests/Unit/IO/H5/Test_CombineH5.py b/tests/Unit/IO/H5/Test_CombineH5.py new file mode 100644 index 000000000000..83d150ae533b --- /dev/null +++ b/tests/Unit/IO/H5/Test_CombineH5.py @@ -0,0 +1,159 @@ +# Distributed under the MIT License. +# See LICENSE.txt for details. + +import os +import unittest + +import numpy as np + +import spectre.IO.H5 as spectre_h5 +from spectre import Informer +from spectre.DataStructures import DataVector +from spectre.IO.H5 import ElementVolumeData, TensorComponent, combine_h5 +from spectre.Spectral import Basis, Quadrature + + +class TestCombineH5(unittest.TestCase): + # Test Fixtures + def setUp(self): + # The tests in this class combine 2 HDF5 files to generate one, + # using the Combine_H5 functionality. + self.file_name1 = os.path.join( + Informer.unit_test_build_path(), "IO/TestVolumeData0.h5" + ) + self.file_name2 = os.path.join( + Informer.unit_test_build_path(), "IO/TestVolumeData1.h5" + ) + + self.output_file = os.path.join( + Informer.unit_test_build_path(), "IO/TestOutput" + ) + + if os.path.isfile(self.file_name1): + os.remove(self.file_name1) + if os.path.isfile(self.file_name2): + os.remove(self.file_name2) + if os.path.isfile(self.output_file + "0.h5"): + os.remove(self.output_file + "0.h5") + + # Initializing attributes + grid_names1 = ["[B0(L0I0,L0I0,L1I0)]"] + grid_names2 = ["[B0(L1I0,L0I0,L0I0)]"] + observation_values = {0: 7.0, 1: 1.3} + basis = Basis.Legendre + quad = Quadrature.Gauss + self.observation_ids = [0, 1] + + # Writing ElementVolume data and TensorComponent Data to first file + self.h5_file1 = spectre_h5.H5File(file_name=self.file_name1, mode="a") + self.tensor_component_data1 = np.array([[0.0], [0.3], [0.6]]) + self.h5_file1.insert_vol("/element_data", version=0) + self.h5_file1.close_current_object() + self.vol_file1 = self.h5_file1.get_vol(path="/element_data") + + self.element_vol_data_file_1 = [ + ElementVolumeData( + element_name=grid_names1[0], + components=[ + TensorComponent( + "field_1", DataVector(self.tensor_component_data1[i]) + ), + TensorComponent( + "field_2", + DataVector(self.tensor_component_data1[i + 1]), + ), + ], + extents=3 * [2], + basis=3 * [basis], + quadrature=3 * [quad], + ) + for i, observation_id in enumerate(self.observation_ids) + ] + + # Write extents and tensor volume data to the volfile + for i, observation_id in enumerate(self.observation_ids): + self.vol_file1.write_volume_data( + observation_id, + observation_values[observation_id], + [ + self.element_vol_data_file_1[i], + ], + ) + + # Store initial connectivity data + self.initial_h5_connectivity = self.vol_file1.get_tensor_component( + self.observation_ids[0], "connectivity" + ).data + + self.h5_file1.close() + + # Writing ElementVolume data and TensorComponent Data to second file + self.h5_file2 = spectre_h5.H5File(file_name=self.file_name2, mode="a") + self.tensor_component_data2 = np.array([[1.0], [0.13], [0.16]]) + self.h5_file2.insert_vol("/element_data", version=0) + self.h5_file2.close_current_object() + self.vol_file2 = self.h5_file2.get_vol(path="/element_data") + + self.element_vol_data_file_2 = [ + ElementVolumeData( + element_name=grid_names2[0], + components=[ + TensorComponent( + "field_1", DataVector(self.tensor_component_data2[i]) + ), + TensorComponent( + "field_2", + DataVector(self.tensor_component_data2[i + 1]), + ), + ], + extents=3 * [2], + basis=3 * [basis], + quadrature=3 * [quad], + ) + for i, observation_id in enumerate(self.observation_ids) + ] + + # Write extents and tensor volume data to the volfile + for i, observation_id in enumerate(self.observation_ids): + self.vol_file2.write_volume_data( + observation_id, + observation_values[observation_id], + [ + self.element_vol_data_file_2[i], + ], + ) + self.h5_file2.close() + + def tearDown(self): + self.h5_file1.close() + self.h5_file2.close() + if os.path.isfile(self.file_name1): + os.remove(self.file_name1) + if os.path.isfile(self.file_name2): + os.remove(self.file_name2) + if os.path.isfile(self.output_file + "0.h5"): + os.remove(self.output_file + "0.h5") + + 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_name1[:-4], "element_data", self.output_file) + h5_output = spectre_h5.H5File( + file_name=self.output_file + "0.h5", mode="a" + ) + output_vol = h5_output.get_vol(path="/element_data") + + # Extracts the connectivity data from the volume file + # If length of final connectivity is more, combine_h5 + # has successfully merged the two HDF5 files. + + final_h5_connectivity = output_vol.get_tensor_component( + self.observation_ids[0], "connectivity" + ).data + + assert len(self.initial_h5_connectivity) < len(final_h5_connectivity) + + +if __name__ == "__main__": + unittest.main(verbosity=2)