From 30899d594fd2eeaa9c03e52d533b0f1e890c8e1f Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Fri, 19 May 2023 10:05:56 +0100 Subject: [PATCH] `DispersiveMultiLayer` no longer uses MATLAB (#286) * Rework DispersiveMultiLayer class into a struct. TDMS builds. - xyz_vector struct of 3 vectors introduced - DispersiveMultiLayer is now a struct with xyz_vector members - Preserve method for checking if the medium is dispersive - Temporarily disable unit tests for DispersiveMultiLayer class - HDF5Reader::read() can now assemble a DispersiveMultiLayer - HDF5Reader has a method for reading directly into a vector - H5Dimension can be passed a H5::DataSet as well as a H5::DataSpace * Update paths now we need HDF5 data as well as matlab data * TDMS builds and tests HDF5Reader::read_data_from_group (passes, no) * Fix seg-fault in read_dataset_from_group * Add DispersiveMultiLayer test * Update DispersiveMultiLayer tests to not be entangled with the MATLAB jargon * Update the CI so things actually work * Move content of benchmark_scripts readme into doc/developers * Update CI to produce unit test `.mat` and `.hdf5` data (#289) * Move content of benchmark_scripts readme into doc/developers * Update CI attempt 1 * Actually use the right syntax * Force python version to prevent MacOS using python2.7 * Rename files to better match the data they produce * Aplease windows syntax * Remove pip cache due to known windows bug: https://github.com/actions/setup-python/issues/436 * Apply suggestions from code review --- .github/workflows/ci.yml | 22 +++- .gitignore | 1 + doc/developers.md | 17 +++- tdms/include/arrays.h | 26 ++--- tdms/include/hdf5_io/hdf5_dimension.h | 8 ++ tdms/include/hdf5_io/hdf5_reader.h | 45 +++++++- tdms/src/arrays.cpp | 23 +---- tdms/src/hdf5_io/hdf5_reader.cpp | 27 +++++ .../objects_from_infile.cpp | 8 +- tdms/tests/include/array_test_class.h | 18 ---- tdms/tests/include/unit_test_utils.h | 51 ++++------ .../array_tests/test_DispersiveMultiLayer.cpp | 96 +++++------------- .../.clang-format | 0 .../benchmark_test_BLi_vs_cubic_validation.m | 0 .../benchmark_test_field_interpolation_E.m | 0 .../benchmark_test_field_interpolation_H.m | 0 .../benchmark_test_interpolation_functions.m | 0 .../create_bad_class_data.m} | 2 +- .../create_class_data.m} | 17 +++- .../create_hdf5_test_file.py | 42 ++++++++ .../create_structure_array.m | 9 +- .../benchmark_scripts/hdf5_test_file.hdf5 | Bin 0 -> 5024 bytes .../unit/benchmark_scripts/setup_unit_tests.m | 9 ++ .../test_hdf5_DispersiveMultiLayer.cpp | 48 +++++++++ .../unit/hdf5_io_tests/test_hdf5_reader.cpp | 70 ++++++++++++- .../unit/matlab_benchmark_scripts/readme.md | 17 ---- .../setup_unit_tests.m | 7 -- 27 files changed, 367 insertions(+), 196 deletions(-) rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/.clang-format (100%) rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/benchmark_test_BLi_vs_cubic_validation.m (100%) rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/benchmark_test_field_interpolation_E.m (100%) rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/benchmark_test_field_interpolation_H.m (100%) rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/benchmark_test_interpolation_functions.m (100%) rename tdms/tests/unit/{matlab_benchmark_scripts/create_bad_tdms_object_data.m => benchmark_scripts/create_bad_class_data.m} (94%) rename tdms/tests/unit/{matlab_benchmark_scripts/create_tdms_object_data.m => benchmark_scripts/create_class_data.m} (76%) create mode 100644 tdms/tests/unit/benchmark_scripts/create_hdf5_test_file.py rename tdms/tests/unit/{matlab_benchmark_scripts => benchmark_scripts}/create_structure_array.m (61%) create mode 100644 tdms/tests/unit/benchmark_scripts/hdf5_test_file.hdf5 create mode 100644 tdms/tests/unit/benchmark_scripts/setup_unit_tests.m create mode 100644 tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_DispersiveMultiLayer.cpp delete mode 100644 tdms/tests/unit/matlab_benchmark_scripts/readme.md delete mode 100644 tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12f7919b6..4f8aba8a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,10 @@ jobs: - name: Set up MATLAB uses: matlab-actions/setup-matlab@v1.2.3 - - name: Produce MATLAB unit test data - uses: matlab-actions/run-command@v1 + - name: Setup Python + uses: actions/setup-python@v4 with: - command: cd('tdms/tests/unit/matlab_benchmark_scripts/'), setup_unit_tests + python-version: '3.9' # ------------------------------------------------------------------------------- # Ubuntu @@ -128,6 +128,20 @@ jobs: run: | cmake --build . --config ${{ matrix.build_type }} + # ------------------------------------------------------------------------------- + # Unit tests + - name: Produce MATLAB unit test data + if: matrix.build_testing == 'ON' + uses: matlab-actions/run-command@v1 + with: + command: cd('tdms/tests/unit/benchmark_scripts/'), setup_unit_tests + + - name: Produce hdf5 unit test data + if: matrix.build_testing == 'ON' + run: | + pip install -r ${{ github.workspace }}/tdms/tests/requirements.txt + python ${{ github.workspace }}/tdms/tests/unit/benchmark_scripts/create_hdf5_test_file.py + - name: Run TDMS unit tests if: matrix.build_testing == 'ON' working-directory: ${{ runner.workspace }}/build @@ -153,6 +167,8 @@ jobs: if: matrix.build_testing == 'ON' uses: codecov/codecov-action@v3 + # ------------------------------------------------------------------------------- + # Upload build artefact for system tests - name: Tar the build result to maintain permissions # https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files # https://github.com/actions/upload-artifact/issues/38 diff --git a/.gitignore b/.gitignore index da1e8fa68..e2a28d0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ html/ **/.pytest_cache/ tdms/tests/system/data/*.zip **.mat +tdms/tests/unit/benchmark_scripts/unit_test_data/** # text editor files .idea/ diff --git a/doc/developers.md b/doc/developers.md index f1fe6d244..f5a363d70 100644 --- a/doc/developers.md +++ b/doc/developers.md @@ -254,7 +254,22 @@ The doxygen-style comments will be included in this developer documentation. To run the unit tests, [compile](#compiling) with `-DBUILD_TESTING=ON`. Then run `ctest` from the build directory or execute the test executable `./tdms_tests`. -It's good practice, and reassuring for your pull-request reviewers, if new C++ functionality is at covered by unit tests. +It's good practice, and reassuring for your pull-request reviewers, if new C++ functionality is covered by unit tests. + +#### Benchmark Scripts and Data Generation + +The `tdms/tests/unit/benchmarking` directory contains scripts that produce input data for the unit tests, or that provide benchmarks for the units that are tested. + +The `C++` unit tests require the presence of a `.mat` or `.hdf5` file to read/write data from/to during testing. +The locations of these files are coded into `tests/include/unit_test_utils.h`, but the files themselves are not committed to the repository - they can be created by running the `setup_unit_tests.m` and `create_hdf5_data.py` scripts. +These scripts can then be updated to add/remove/edit the data available to the unit tests: +- `create_hdf5_test_file.py` : Produces `hdf5_test_file.hdf5`; used when testing read/writing from `.hdf5` files. +- `create_structure_array.m` : Produces `structure_array.mat`; used when testing reading/writing MATLAB `struct` arrays. +- `create_class_data.m` : Produces `class_data.mat`; used when testing reading/writing `tdms` objects to/from `.mat` files. +- `create_bad_class_data.m` : Produces `bad_class_data.mat`; used for testing failure cases when reading/writing `tdms` objects to/from `.mat` files. + +The `benchmark_` scripts perform band-limited interpolation (BLi) using `MATLAB`'s `interp` function. +`TDMS`'s interpolation schemes are based off this `MATLAB` function (specficially, in the coefficients the scheme uses to interpolate), and are thus used to benchmark the accuracy of the scheme. ### Test coverage {#coverage} diff --git a/tdms/include/arrays.h b/tdms/include/arrays.h index 0abd57257..efdac0de7 100644 --- a/tdms/include/arrays.h +++ b/tdms/include/arrays.h @@ -15,6 +15,13 @@ #include "matlabio.h" #include "utils.h" +template +struct xyz_vector { + std::vector x = {}; + std::vector y = {}; + std::vector z = {}; +}; + template class XYZTensor3D { public: @@ -202,28 +209,23 @@ class DMaterial : public DCollectionBase, MaterialCollection { explicit DMaterial(const mxArray *ptr); }; -class DispersiveMultiLayer { -public: - double *alpha = nullptr; - double *beta = nullptr; - double *gamma = nullptr; - XYZVectors kappa; - XYZVectors sigma; - +struct DispersiveMultiLayer { public: - explicit DispersiveMultiLayer(const mxArray *ptr); + std::vector alpha; + std::vector beta; + std::vector gamma; + xyz_vector kappa; + xyz_vector sigma; /** * @brief Determines whether the (background) medium is dispersive * - * @param K_tot Number of Yee cells in the z-direction (number of entries in - * this->gamma) * @param near_zero_tolerance Tolerance for non-zero gamma (attenuation) * values * @return true Background is dispersive * @return false Background is not dispersive */ - bool is_dispersive(int K_tot, double near_zero_tolerance = 1e-15); + bool is_dispersive(double near_zero_tolerance = 1e-15) const; }; template diff --git a/tdms/include/hdf5_io/hdf5_dimension.h b/tdms/include/hdf5_io/hdf5_dimension.h index a508164db..63b202d35 100644 --- a/tdms/include/hdf5_io/hdf5_dimension.h +++ b/tdms/include/hdf5_io/hdf5_dimension.h @@ -9,6 +9,7 @@ class H5Dimension : public std::vector { public: H5Dimension() = default; H5Dimension(const H5::DataSpace &data_space); + H5Dimension(const H5::DataSet &data_set) : H5Dimension(data_set.getSpace()){}; /** * @brief Whether these dimensions describe an array that is castable to a 1D @@ -31,4 +32,11 @@ class H5Dimension : public std::vector { * @return hsize_t */ hsize_t max_dim() const { return *std::max_element(begin(), end()); }; + + /** @brief The total number of elements (product of the dimensions) */ + int number_of_elements() const { + int product = 1; + for (hsize_t axis_size : *this) { product *= axis_size; } + return product; + } }; diff --git a/tdms/include/hdf5_io/hdf5_reader.h b/tdms/include/hdf5_io/hdf5_reader.h index 76fe80b89..d890a8c80 100644 --- a/tdms/include/hdf5_io/hdf5_reader.h +++ b/tdms/include/hdf5_io/hdf5_reader.h @@ -23,9 +23,9 @@ class HDF5Reader : public HDF5Base { : HDF5Base(filename, H5F_ACC_RDONLY) {} /** - * @brief Read the dataset stored within a group into the buffer provided. Can - * be used to read MATLAB structs by treating the struct as the Group and - * field as the Dataset. + * @brief Read the dataset stored within a group into the buffer provided. + * @details Can be used to read MATLAB structs by treating the struct as the + * Group and field as the Dataset. * @tparam T C++ datatype to read data into. * @param group The Group within the file in which the dataset lives. * @param dataset The name of the dataset to fetch data from. @@ -44,6 +44,34 @@ class HDF5Reader : public HDF5Base { requested_field.read(data, requested_field.getDataType()); } + /** + * @brief Read the dataset stored within a group into the buffer provided, + * resizing the vector buffer accordingly. + * @details Can be used to read MATLAB structs by treating the struct as the + * Group and field as the Dataset. + * @tparam T C++ datatype to read data into. + * @param group The Group within the file in which the dataset lives. + * @param dataset The name of the dataset to fetch data from. + * @param[out] data The buffer into which to write the data. + */ + template + void read_dataset_in_group(const std::string &group, + const std::string &dataset, + std::vector &data) const { + spdlog::debug("Reading {} from file: {}", group, filename_); + + // Structs are saved as groups, so we need to fetch the group this struct is + // contained in + H5::Group structure_array = file_->openGroup(group); + // Then fetch the requested data and read it into the buffer provided, + // resizing the buffer if necessary + H5::DataSet requested_field = structure_array.openDataSet(dataset); + H5Dimension field_size(requested_field); + int number_of_elements = field_size.number_of_elements(); + data.resize(number_of_elements); + requested_field.read(data.data(), requested_field.getDataType()); + } + /** * @brief Reads a named dataset from the HDF5 file. * @param dataname The name of the datset to be read. @@ -139,4 +167,15 @@ class HDF5Reader : public HDF5Base { * @param cube */ void read(Cuboid *cube) const; + + /** + * @brief Read data from the file into a DispersiveMultiLayerObject + * @details Data is read from the "dispersive_aux" group. If this group does + * not exist, no data is written but no exception is thrown. + * + * If the group does exist, the alpha, beta, gamma, kappa, and sigma members + * are populated with the corresponding data entries. + * @param dml DispersiveMultiLayer object into which to write data. + */ + void read(DispersiveMultiLayer *dml) const; }; diff --git a/tdms/src/arrays.cpp b/tdms/src/arrays.cpp index e34fade35..d30c48c77 100644 --- a/tdms/src/arrays.cpp +++ b/tdms/src/arrays.cpp @@ -162,26 +162,9 @@ void DCollection::init_xyz_vectors(const mxArray *ptr, XYZVectors &arrays, } } -DispersiveMultiLayer::DispersiveMultiLayer(const mxArray *ptr) { - - if (mxIsEmpty(ptr)) { return; } - assert_is_struct_with_n_fields(ptr, 9, "dispersive_aux"); - - alpha = mxGetPr(ptr_to_vector_in(ptr, "alpha", "dispersive_aux")); - beta = mxGetPr(ptr_to_vector_in(ptr, "beta", "dispersive_aux")); - gamma = mxGetPr(ptr_to_vector_in(ptr, "gamma", "dispersive_aux")); - kappa.x = mxGetPr(ptr_to_matrix_in(ptr, "kappa_x", "dispersive_aux")); - kappa.y = mxGetPr(ptr_to_matrix_in(ptr, "kappa_y", "dispersive_aux")); - kappa.z = mxGetPr(ptr_to_matrix_in(ptr, "kappa_z", "dispersive_aux")); - sigma.x = mxGetPr(ptr_to_matrix_in(ptr, "sigma_x", "dispersive_aux")); - sigma.y = mxGetPr(ptr_to_matrix_in(ptr, "sigma_y", "dispersive_aux")); - sigma.z = mxGetPr(ptr_to_matrix_in(ptr, "sigma_z", "dispersive_aux")); -} - -bool DispersiveMultiLayer::is_dispersive(int K_tot, - double near_zero_tolerance) { - for (int i = 0; i < K_tot; i++) { - if (fabs(gamma[i]) > near_zero_tolerance) { +bool DispersiveMultiLayer::is_dispersive(double near_zero_tolerance) const { + for (double gamma_val : gamma) { + if (fabs(gamma_val) > near_zero_tolerance) { // non-zero attenuation constant of a Yee cell implies media is dispersive return true; } diff --git a/tdms/src/hdf5_io/hdf5_reader.cpp b/tdms/src/hdf5_io/hdf5_reader.cpp index ea48a7717..95de8f6be 100644 --- a/tdms/src/hdf5_io/hdf5_reader.cpp +++ b/tdms/src/hdf5_io/hdf5_reader.cpp @@ -4,6 +4,8 @@ #include #include +#include + using namespace std; void HDF5Reader::read(const string &plane, InterfaceComponent *ic) const { @@ -52,3 +54,28 @@ void HDF5Reader::read(Cuboid *cube) const { cube->array[i] = (int) intermediate_buffer[i] - 1; } } + +void HDF5Reader::read(DispersiveMultiLayer *dml) const { + string group_name = "dispersive_aux"; + // Deal with the case of an empty input + if (!file_->nameExists(group_name)) { + spdlog::info(group_name + " is not a group: assuming empty input"); + return; + } else { + // This is a group - it should have 9 members and we can quickly check this + H5::Group dispersive_aux = file_->openGroup(group_name); + if (dispersive_aux.getNumObjs() != 9) { + throw runtime_error("dispersive_aux does not have exactly 9 members!"); + } + } + // Assuming non-empty input, setup the data appropriately + read_dataset_in_group(group_name, "alpha", dml->alpha); + read_dataset_in_group(group_name, "beta", dml->beta); + read_dataset_in_group(group_name, "gamma", dml->gamma); + read_dataset_in_group(group_name, "kappa_x", dml->kappa.x); + read_dataset_in_group(group_name, "kappa_y", dml->kappa.y); + read_dataset_in_group(group_name, "kappa_z", dml->kappa.z); + read_dataset_in_group(group_name, "sigma_x", dml->sigma.x); + read_dataset_in_group(group_name, "sigma_y", dml->sigma.y); + read_dataset_in_group(group_name, "sigma_z", dml->sigma.z); +} diff --git a/tdms/src/simulation_manager/objects_from_infile.cpp b/tdms/src/simulation_manager/objects_from_infile.cpp index 0c508f816..f3cddc129 100644 --- a/tdms/src/simulation_manager/objects_from_infile.cpp +++ b/tdms/src/simulation_manager/objects_from_infile.cpp @@ -17,9 +17,7 @@ IndependentObjectsFromInfile::IndependentObjectsFromInfile( Dmaterial(matrices_from_input_file["Dmaterial"]),// get Dmaterial C(matrices_from_input_file["C"]), // get C D(matrices_from_input_file["D"]), // get D - matched_layer( - matrices_from_input_file["dispersive_aux"]),// get dispersive_aux - Ei(matrices_from_input_file["tdfield"]) // get tdfield + Ei(matrices_from_input_file["tdfield"]) // get tdfield { /* Set FDTD/PSTD-dependent variable skip_tdf [1: PSTD, 6: FDTD] */ skip_tdf = in_flags["use_pstd"] ? 1 : 6; @@ -140,9 +138,7 @@ IndependentObjectsFromInfile::IndependentObjectsFromInfile( } // work out if we have a dispersive background - if (params.is_disp_ml) { - params.is_disp_ml = matched_layer.is_dispersive(IJK_tot.k); - } + if (params.is_disp_ml) { params.is_disp_ml = matched_layer.is_dispersive(); } // Set dt so that an integer number of time periods fits within a sinusoidal // period diff --git a/tdms/tests/include/array_test_class.h b/tdms/tests/include/array_test_class.h index c1dfa391c..7f71a4d2c 100644 --- a/tdms/tests/include/array_test_class.h +++ b/tdms/tests/include/array_test_class.h @@ -60,24 +60,6 @@ class DetectorSensitivityArraysTest : public AbstractArrayTest { std::string get_class_name() override { return "DetectorSensitivityArray"; } }; -/** @brief Unit tests for DispersiveMultilayer */ -class DispersiveMultilayerTest : public AbstractArrayTest { -private: - const int n_fields = 9; - const char *fieldnames[9] = {"alpha", "beta", "gamma", - "kappa_x", "kappa_y", "kappa_z", - "sigma_x", "sigma_y", "sigma_z"}; - - void test_empty_construction() override; - void test_wrong_input_type() override; - void test_correct_construction() override; - // test: is_dispersive() - void test_other_methods() override; - -public: - std::string get_class_name() override { return "DispersiveMultilayer"; } -}; - // Test methods check the performance of initialise, as this is the de-facto // constructor class DTildeTest : public AbstractArrayTest { diff --git a/tdms/tests/include/unit_test_utils.h b/tdms/tests/include/unit_test_utils.h index 70084fcd0..4ca72fcdb 100644 --- a/tdms/tests/include/unit_test_utils.h +++ b/tdms/tests/include/unit_test_utils.h @@ -17,41 +17,30 @@ namespace tdms_unit_test_data { #ifdef CMAKE_SOURCE_DIR inline std::string tdms_object_data(std::string(CMAKE_SOURCE_DIR) + - "/tests/unit/matlab_benchmark_scripts/" - "matlab_data/class_data.mat"); + "/tests/unit/benchmark_scripts/" + "unit_test_data/class_data.mat"); inline std::string tdms_bad_object_data(std::string(CMAKE_SOURCE_DIR) + - "/tests/unit/matlab_benchmark_scripts/" - "matlab_data/bad_class_data.mat"); -#else -inline std::string tdms_object_data(std::filesystem::current_path() / - "../tests/unit/matlab_benchmark_scripts/" - "matlab_data/class_data.mat"); -inline std::string - tdms_bad_object_data(std::filesystem::current_path() / - "../tests/unit/matlab_benchmark_scripts/" - "matlab_data/bad_class_data.mat"); -#endif - -/* The struct_testdata is a .mat file containing a single structure array, -example_struct. This structure array has the following data. - -MEMBER: value, MATLAB type -double_no_decimal: 1.0, double -double_half: 0.5, double -string: 'tdms', char array -boolean: 1, logical -uint_345: 3 * 4 * 5 array of 1s, uint8 -double_22: [0.25, 0.5; 0.75, 1.], double -complex_22: [0., -i; i, 0], complex -*/ -#ifdef CMAKE_SOURCE_DIR + "/tests/unit/benchmark_scripts/" + "unit_test_data/bad_class_data.mat"); +inline std::string hdf5_test_file(std::string(CMAKE_SOURCE_DIR) + + "/tests/unit/benchmark_scripts/" + "unit_test_data/hdf5_test_file.hdf5"); inline std::string struct_testdata(std::string(CMAKE_SOURCE_DIR) + - "/tests/unit/matlab_benchmark_scripts/" - "matlab_data/structure_array.mat"); + "/tests/unit/benchmark_scripts/" + "unit_test_data/structure_array.mat"); #else +inline std::string tdms_object_data(std::filesystem::current_path() / + "../tests/unit/benchmark_scripts/" + "unit_test_data/class_data.mat"); +inline std::string tdms_bad_object_data(std::filesystem::current_path() / + "../tests/unit/benchmark_scripts/" + "unit_test_data/bad_class_data.mat"); +inline std::string hdf5_test_file(std::filesystem::current_path() / + "../tests/unit/benchmark_scripts/" + "unit_test_data/hdf5_test_file.hdf5"); inline std::string struct_testdata(std::filesystem::current_path() / - "../tests/unit/matlab_benchmark_scripts/" - "matlab_data/structure_array.mat"); + "../tests/unit/benchmark_scripts/" + "unit_test_data/structure_array.mat"); #endif }// namespace tdms_unit_test_data diff --git a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp index 4a45a8856..ac3437a56 100644 --- a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp +++ b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp @@ -1,9 +1,8 @@ /** * @file test_DispersiveMultiLayer.cpp * @author William Graham (ccaegra@ucl.ac.uk) - * @brief Tests for the DispersiveMultiLayer class and its subclasses + * @brief Tests for the DispersiveMultiLayer struct and its methods */ -#include #include #include @@ -12,81 +11,32 @@ #include "unit_test_utils.h" using namespace std; -using Catch::Approx; -using tdms_tests::TOLERANCE; -void DispersiveMultilayerTest::test_empty_construction() { - // Constructor should error if recieving a struct with no fields - create_1by1_struct(0, {}); - REQUIRE_THROWS_AS(DispersiveMultiLayer(matlab_input), runtime_error); -} +/** @brief Test the methods associated to the DispersiveMultiLayer struct */ +TEST_CASE("DispersiveMultiLayer") { + spdlog::debug("[Unit] DispersiveMultiLayer"); -void DispersiveMultilayerTest::test_wrong_input_type() { - // Constructor should throw runtime_error at not recieving struct - dimensions_2d[0] = 2; - dimensions_2d[1] = 3; - create_numeric_array(2, dimensions_2d, mxUINT16_CLASS); - REQUIRE_THROWS_AS(DispersiveMultiLayer(matlab_input), runtime_error); -} + int N_ELEMENTS = 5; + DispersiveMultiLayer dml; -void DispersiveMultilayerTest::test_correct_construction() { - create_1by1_struct(n_fields, fieldnames); - // build "data" for each of the fields, which is going to be the same array - // filled with consecutive integers - const int array_size[2] = {1, n_numeric_elements}; - mxArray *field_array_ptrs[n_fields]; - for (int i = 0; i < n_fields; i++) { - field_array_ptrs[i] = mxCreateNumericArray(2, (const mwSize *) array_size, - mxDOUBLE_CLASS, mxREAL); - mxDouble *where_to_place_data = mxGetPr(field_array_ptrs[i]); - for (int i = 0; i < n_numeric_elements; i++) { - where_to_place_data[i] = (double) i; - } - mxSetField(matlab_input, 0, fieldnames[i], field_array_ptrs[i]); - } - // we should now be able to create a DispersiveMultiLayer object - REQUIRE_NOTHROW(DispersiveMultiLayer(matlab_input)); - DispersiveMultiLayer dml(matlab_input); - // now check that the data has been correctly assigned - for (int i = 0; i < n_numeric_elements; i++) { - CHECK(dml.alpha[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.beta[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.gamma[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.kappa.x[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.kappa.y[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.kappa.z[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.sigma.x[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.sigma.y[i] == Approx(i).epsilon(TOLERANCE)); - CHECK(dml.sigma.z[i] == Approx(i).epsilon(TOLERANCE)); - } -} - -void DispersiveMultilayerTest::test_other_methods() { + // Test the is_dispersive() method SECTION("is_dispersive()") { - create_1by1_struct(n_fields, fieldnames); - // build "data" for each of the fields, just a constant array of 1s - const int array_size[2] = {1, n_numeric_elements}; - mxArray *field_array_ptrs[n_fields]; - for (int i = 0; i < n_fields; i++) { - field_array_ptrs[i] = mxCreateNumericArray(2, (const mwSize *) array_size, - mxDOUBLE_CLASS, mxREAL); - mxDouble *where_to_place_data = mxGetPr(field_array_ptrs[i]); - for (int i = 0; i < n_numeric_elements; i++) { - where_to_place_data[i] = 1.; - } - mxSetField(matlab_input, 0, fieldnames[i], field_array_ptrs[i]); + SECTION("non-empty vector") { + /* Initialise the gamma component and set to 0 */ + dml.gamma.resize(N_ELEMENTS); + // All entries being zero should result in a non-dispersive medium + REQUIRE(!dml.is_dispersive()); + + // Populate an element with the value 1. + dml.gamma[N_ELEMENTS / 2] = 1.; + /* A tolerance of 1.5 should still flag the dml as not dispersive */ + REQUIRE(!dml.is_dispersive(1.5)); + /* Yet a tolerance of 0.5 should flag it as dispersive */ + REQUIRE(dml.is_dispersive(0.5)); + } + SECTION("empty vector") { + /* When empty, is_dispersive() should not error and return false */ + REQUIRE(!dml.is_dispersive()); } - // create DispersiveMultiLayer object - DispersiveMultiLayer dml(matlab_input); - - // all entries in gamma are 1. -> so a tolerance of 1.5 should flag the dml - // as not dispersive - REQUIRE(!dml.is_dispersive(n_numeric_elements, 1.5)); - // yet a tolerance of 0.5 should flag it as dispersive - REQUIRE(dml.is_dispersive(n_numeric_elements, 0.5)); } } - -TEST_CASE("DispersiveMultiLayer") { - DispersiveMultilayerTest().run_all_class_tests(); -} diff --git a/tdms/tests/unit/matlab_benchmark_scripts/.clang-format b/tdms/tests/unit/benchmark_scripts/.clang-format similarity index 100% rename from tdms/tests/unit/matlab_benchmark_scripts/.clang-format rename to tdms/tests/unit/benchmark_scripts/.clang-format diff --git a/tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_BLi_vs_cubic_validation.m b/tdms/tests/unit/benchmark_scripts/benchmark_test_BLi_vs_cubic_validation.m similarity index 100% rename from tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_BLi_vs_cubic_validation.m rename to tdms/tests/unit/benchmark_scripts/benchmark_test_BLi_vs_cubic_validation.m diff --git a/tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_field_interpolation_E.m b/tdms/tests/unit/benchmark_scripts/benchmark_test_field_interpolation_E.m similarity index 100% rename from tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_field_interpolation_E.m rename to tdms/tests/unit/benchmark_scripts/benchmark_test_field_interpolation_E.m diff --git a/tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_field_interpolation_H.m b/tdms/tests/unit/benchmark_scripts/benchmark_test_field_interpolation_H.m similarity index 100% rename from tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_field_interpolation_H.m rename to tdms/tests/unit/benchmark_scripts/benchmark_test_field_interpolation_H.m diff --git a/tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_interpolation_functions.m b/tdms/tests/unit/benchmark_scripts/benchmark_test_interpolation_functions.m similarity index 100% rename from tdms/tests/unit/matlab_benchmark_scripts/benchmark_test_interpolation_functions.m rename to tdms/tests/unit/benchmark_scripts/benchmark_test_interpolation_functions.m diff --git a/tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m b/tdms/tests/unit/benchmark_scripts/create_bad_class_data.m similarity index 94% rename from tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m rename to tdms/tests/unit/benchmark_scripts/create_bad_class_data.m index 53ba6f504..a233504f1 100644 --- a/tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m +++ b/tdms/tests/unit/benchmark_scripts/create_bad_class_data.m @@ -17,4 +17,4 @@ % Save the files to the expected filename for the unit tests to read the % data back in. -save("matlab_data/bad_class_data.mat", "-v7.3"); +save("unit_test_data/bad_class_data.mat", "-v7.3"); diff --git a/tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m b/tdms/tests/unit/benchmark_scripts/create_class_data.m similarity index 76% rename from tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m rename to tdms/tests/unit/benchmark_scripts/create_class_data.m index 693f01603..096be2e0b 100644 --- a/tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m +++ b/tdms/tests/unit/benchmark_scripts/create_class_data.m @@ -33,8 +33,23 @@ % This array is read into the Cuboid class. It is just an array of 6 integers (stored as doubles OFC) that correspond to Yee cell indices in the various axial directions phasorsurface = [1 4 2 5 3 6]; +%% dispersive_aux +% Structure array that populates a DispersiveMultiLayer object. +% Has 9 fields, which are all vectors (theoretically of the same length but for testing purposes nope): +% alpha, beta, gamma, kappa_x, kappa_y, kappa_z, sigma_x, sigma_y, sigma_z +dispersive_aux = struct(); +dispersive_aux.alpha = 0:9; +dispersive_aux.beta = 0:9; +dispersive_aux.gamma = 0:9; +dispersive_aux.kappa_x = 0:9; +dispersive_aux.kappa_y = 0:9; +dispersive_aux.kappa_z = 0:9; +dispersive_aux.sigma_x = 0:9; +dispersive_aux.sigma_y = 0:9; +dispersive_aux.sigma_z = 0:9; + %% save variables to the file we need % Save the files to the expected filename for the unit tests to read the % data back in. -save("matlab_data/class_data.mat", "-v7.3"); +save("unit_test_data/class_data.mat", "-v7.3"); diff --git a/tdms/tests/unit/benchmark_scripts/create_hdf5_test_file.py b/tdms/tests/unit/benchmark_scripts/create_hdf5_test_file.py new file mode 100644 index 000000000..be43b2c56 --- /dev/null +++ b/tdms/tests/unit/benchmark_scripts/create_hdf5_test_file.py @@ -0,0 +1,42 @@ +import os +import sys + +import h5py +import numpy as np + +FNAME_TO_CREATE = os.path.abspath( + os.path.dirname(__file__) + "/unit_test_data/hdf5_test_file.hdf5" +) + + +def create_hdf5_test_file() -> None: + """ """ + # Create the directory for the file (if it doesn't exist), and the file itself + if not os.path.exists(os.path.dirname(FNAME_TO_CREATE)): + os.mkdir(os.path.dirname(FNAME_TO_CREATE)) + file = h5py.File(FNAME_TO_CREATE, "w") + + # Create a group under root + read_in_test = file.require_group("read_in_test") + + # Create test data to read in + consecutive_numbers = np.arange(0, 12, dtype=float) + + # Populate group with test data + read_in_test.create_dataset( + "vector_int", data=consecutive_numbers, shape=(12,), dtype=int + ) + read_in_test.create_dataset( + "matrix_double", data=consecutive_numbers, shape=(2, 6), dtype=float + ) + read_in_test.create_dataset( + "tensor_double", data=consecutive_numbers, shape=(2, 3, 2), dtype=float + ) + + file.close() + return + + +if __name__ == "__main__": + create_hdf5_test_file() + sys.exit(0) diff --git a/tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m b/tdms/tests/unit/benchmark_scripts/create_structure_array.m similarity index 61% rename from tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m rename to tdms/tests/unit/benchmark_scripts/create_structure_array.m index 2bdcf8559..79528fabc 100644 --- a/tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m +++ b/tdms/tests/unit/benchmark_scripts/create_structure_array.m @@ -2,6 +2,7 @@ close all; clear; +%% An example to test how different matlab datatypes are stored in HDF5 files example_struct = struct(); example_struct.double_no_decimal = 1.; @@ -12,8 +13,14 @@ example_struct.double_22 = [0.25, 0.5; 0.75, 1.]; example_struct.complex_22 = [0., -1.i; 1.i, 0.]; +%% An example to check that buffers are read in correctly from .mat files +read_in_test = struct(); +read_in_test.vector = int32(0:11); +read_in_test.matrix = reshape(0:11, 2, 6); +read_in_test.tensor = reshape(0:11, 2, 3, 2); + %% save variables to the file we need % Save the files to the expected filename for the unit tests to read the % data back in. -save("matlab_data/structure_array.mat", "example_struct", "-v7.3"); +save("unit_test_data/structure_array.mat", "-v7.3"); diff --git a/tdms/tests/unit/benchmark_scripts/hdf5_test_file.hdf5 b/tdms/tests/unit/benchmark_scripts/hdf5_test_file.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..c96da6360da2a34823babfd58eee815736a94a87 GIT binary patch literal 5024 zcmeHKOHKko5UplJN7Mw=1u-s&F@^)MV8@t%VWmc0xC4|)B=M8LXjrnu9Y=8KQ9K4m z@CMjD^=cA_fWd^Q*geU-uIlQBemth%JXgy{8>yWXndZ8*MCq_Aw-MUQ7H1@u@j5yR z*ayBQxTDBiL|NoBSU+BOaaO62u9Wo!F^z^v?zxh%_^%kKRt`_ihA**e(bF4rCSh$$Ug!q8{7Sz?2-oU&#vfoo%H#SiVqbmeN4ELoUk49u zt>93-M0bs#-Rjnw506*3zAg&<`;H!uV$pX_7Kq{b@k!ZeSag?zZe?Cv$Fb=)1Otka z{44=+3MZOUoLEPlprg+@Gm_TCGxuItk<^jNClZNpqKkDL)mseP@(2iybh0}p_NHt+T2b1Yg0ECZGS%YbFzZ!<9NE7>V2wug})kaJ)B zfyBmv8SjwEl_H;6Sfj!S24b?`98U0?-5(`%oDY6eQVu&L*}wQBi3Q{N@AKoqO4Vb0 J+{EJfh9ASvY!?6k literal 0 HcmV?d00001 diff --git a/tdms/tests/unit/benchmark_scripts/setup_unit_tests.m b/tdms/tests/unit/benchmark_scripts/setup_unit_tests.m new file mode 100644 index 000000000..e38e5b14d --- /dev/null +++ b/tdms/tests/unit/benchmark_scripts/setup_unit_tests.m @@ -0,0 +1,9 @@ +if ~exist('unit_test_data', 'dir') + mkdir('unit_test_data'); +end + +create_structure_array; +create_class_data; +create_bad_class_data; + +exit; diff --git a/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_DispersiveMultiLayer.cpp b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_DispersiveMultiLayer.cpp new file mode 100644 index 000000000..6c1aedb31 --- /dev/null +++ b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_DispersiveMultiLayer.cpp @@ -0,0 +1,48 @@ +#include "hdf5_io/hdf5_reader.h" + +#include + +#include +#include + +#include "unit_test_utils.h" + +using Catch::Approx; +using namespace std; +using tdms_unit_test_data::tdms_object_data; + +TEST_CASE("HDF5: Read DispersiveMultiLayer") { + HDF5Reader MATFile(tdms_object_data); + // read from dispersive_aux group + DispersiveMultiLayer dml; + + SECTION("Correct data") { + vector consecutive_integers(10); + for (int i = 0; i < 10; i++) { consecutive_integers[i] = (double) i; } + + MATFile.read(&dml); + + // Assert correct data - each entry should just be the integers 0->9 + // inclusive + bool correct_data_read = true; + for (int i = 0; i < 10; i++) { + correct_data_read = correct_data_read && dml.alpha[i] == Approx(i) && + dml.beta[i] == Approx(i) && dml.gamma[i] == Approx(i); + } + correct_data_read = correct_data_read && + equal(dml.kappa.x.begin(), dml.kappa.x.end(), + consecutive_integers.begin()) && + equal(dml.kappa.y.begin(), dml.kappa.y.end(), + consecutive_integers.begin()) && + equal(dml.kappa.z.begin(), dml.kappa.z.end(), + consecutive_integers.begin()) && + equal(dml.sigma.x.begin(), dml.sigma.x.end(), + consecutive_integers.begin()) && + equal(dml.sigma.y.begin(), dml.sigma.y.end(), + consecutive_integers.begin()) && + equal(dml.sigma.z.begin(), dml.sigma.z.end(), + consecutive_integers.begin()); + + REQUIRE(correct_data_read); + } +} diff --git a/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp b/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp index 9b887f899..01ea48c0e 100644 --- a/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp +++ b/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp @@ -6,19 +6,22 @@ */ #include "hdf5_io/hdf5_reader.h" +#include #include #include #include #include +#include + #include "unit_test_utils.h" using namespace std; using tdms_tests::uint16s_to_string; -using tdms_unit_test_data::struct_testdata; +using tdms_unit_test_data::struct_testdata, tdms_unit_test_data::hdf5_test_file; -TEST_CASE("Read from a MATLAB struct") { +TEST_CASE("HDF5: Read from a MATLAB struct") { HDF5Reader MATFile(struct_testdata); SECTION("Read numeric scalars") { @@ -71,3 +74,66 @@ TEST_CASE("Read from a MATLAB struct") { // The complex matrix is the Pauli-y matrix [0, -i; i, 0] } } + +/** @brief Test the performance of read_dataset_in_group, on both MATLAB files + * and HDF5 files */ +TEST_CASE("HDF5Reader::read_dataset_in_group") { + vector read_buffer; + read_buffer.reserve(12); + // Used to check that data has been read in correctly + bool entries_read_correctly = true; + + SECTION(".mat files") { + HDF5Reader Hfile(struct_testdata); + + SECTION("Vector [int32]") { + // We do an extra loop over the entries here to ensure that our final + // check confirms that both the int data and the doubles that they were + // cast to are correct + vector int_buffer(12); + Hfile.read_dataset_in_group("read_in_test", "vector", int_buffer.data()); + for (int i = 0; i < 12; i++) { + entries_read_correctly = entries_read_correctly && int_buffer[i] == i; + read_buffer[i] = (double) int_buffer[i]; + } + } + SECTION("Matrix [double]") { + Hfile.read_dataset_in_group("read_in_test", "matrix", read_buffer.data()); + } + SECTION("Tensor [double]") { + Hfile.read_dataset_in_group("read_in_test", "tensor", read_buffer.data()); + } + } + + SECTION(".hdf5 files") { + HDF5Reader Hfile(hdf5_test_file); + + // h5py saves int dtype at 64-bit integers + SECTION("Vector [int64]") { + // We do an extra loop over the entries here to ensure that our final + // check confirms that both the int data and the doubles that they were + // cast to are correct + vector int_buffer(12); + Hfile.read_dataset_in_group("read_in_test", "vector_int", + int_buffer.data()); + for (int i = 0; i < 12; i++) { + entries_read_correctly = entries_read_correctly && int_buffer[i] == i; + read_buffer[i] = (double) int_buffer[i]; + } + } + SECTION("Matrix [double]") { + Hfile.read_dataset_in_group("read_in_test", "matrix_double", + read_buffer.data()); + } + SECTION("Tensor [double]") { + Hfile.read_dataset_in_group("read_in_test", "tensor_double", + read_buffer.data()); + } + } + + for (int i = 0; i < 12; i++) { + entries_read_correctly = + entries_read_correctly && read_buffer[i] == Catch::Approx(i); + } + REQUIRE(entries_read_correctly); +} diff --git a/tdms/tests/unit/matlab_benchmark_scripts/readme.md b/tdms/tests/unit/matlab_benchmark_scripts/readme.md deleted file mode 100644 index 98b86290c..000000000 --- a/tdms/tests/unit/matlab_benchmark_scripts/readme.md +++ /dev/null @@ -1,17 +0,0 @@ -# **`MATLAB` Benchmarking Scripts** - -This directory contains scripts that generate MATLAB `.mat` files for use in the unit tests of TDMS, or provide benchmarks for the units that are tested. - -### Unit test `.mat` data - -A number of our unit tests require the presence of a `.mat` file to read/write data from/to during testing. -Running the following scripts in a MATLAB session within this directory will produce these `.mat` files. - -### Band-limited Interpolation Benchmarking - -The `benchmark_` scripts perform band-limited interpolation (BLi) using `MATLAB`'s `interp` function. -`TDMS`'s interpolation schemes are based off this `MATLAB` function (specficially, in the coefficients the scheme uses to interpolate). - -In order to test that the interpolation is correctly implimented in the `TDMS` source, we provide unit tests that benchmark against `MATLAB`'s implimentations. These scripts are provided here for developer use and documentation. - -Currently; the unit tests have the output error values from `MATLAB` hard-coded into the source, and are required to achieve a comparable level of accuracy for the tests to pass. Each script contains a note referencing which unit test it provides a benchmark for. diff --git a/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m b/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m deleted file mode 100644 index 4700184f8..000000000 --- a/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m +++ /dev/null @@ -1,7 +0,0 @@ -if ~exist('matlab_data', 'dir') - mkdir('matlab_data'); -end - -create_structure_array; -create_tdms_object_data; -create_bad_tdms_object_data;