From eac765d1e1e1c37b1d63049e9649dc753a7a7542 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Fri, 19 May 2023 10:05:16 +0100 Subject: [PATCH] Infrastructure for removing `MATLAB` (#281) * Skeleton for new method * FrequencyVectors use std::vectors instead of our custom object as members. [DOESN'T COMPILE YET] - FrequencyVectors is now just a struct that uses std::vector datatypes - SimualationManager is broken, along with most parts of the codebase that use FrequencyVectors - Pending update to allow code to COMPILE * Allow TDMS to actually be compiled again. [TESTS STILL FAIL, READER METHOD NOT READY YET] - Move .mat data generation scripts for unit tests into benchmarking folder - Adjust paths to unit test data accordingly - InputMatrices stores the filename so that we can minimise disruption as we transition away from MATLAB * IT WORKS * Create the abstract H5Dimensions class to ease reading objects. - Update HDF5Base::shape_of to return instances of this class - Add unit tests for class - Update HDF5Reader method to avoid recycled code * Remove MATLAB pointers to InterfaceComponents in initialisation. - simulation_manager can now instantate these members using HDF5Reader rather than still relying on the old InputMatrices object * Add docstrings to files touched * Produce unit test binaries on CI rather than track in repo * - Detaches MATLAB from Cuboid class. - Turns Cuboid class into a struct because MATLAB is now gone. - Adds failure-case checks to HDF5Reader::read() functions. Squashed commit of the following: commit ce8d15e311e9dbb93bd7d7fbcd6c9cffe69657e3 Author: willGraham01 <1willgraham@gmail.com> Date: Fri May 5 15:19:51 2023 +0100 Rename shapes.h to cuboid.h because that's all it contains commit fe83f568c3da7491d450693574975df90a3f15e1 Author: willGraham01 <1willgraham@gmail.com> Date: Fri May 5 15:13:48 2023 +0100 Fix faulty logic thrown up by test commit b37a89f031519c6a356b50f91a12ad8867b78333 Author: willGraham01 <1willgraham@gmail.com> Date: Fri May 5 15:01:58 2023 +0100 Allow tdms to actually compile commit d7c982b9165e3ae4ccf3f42e0fb8c5cdde55b644 Author: willGraham01 <1willgraham@gmail.com> Date: Fri May 5 14:31:43 2023 +0100 Write unit test and read method for Cuboid. Add failure test for FrequencyVectors - Add new .m script to produce an input file with bad object data - Update unit_test_utils with this new filepath - Write HDF5Reader(Cuboid *cube) method and unit test - Add failure-test check for FrequencyVectors using bad data file commit cf61970d2052ab02ae23822f0edd28e89044734f Author: willGraham01 <1willgraham@gmail.com> Date: Fri May 5 14:12:11 2023 +0100 Change Cuboid to be a struct because a class is overkill * Use one master script for setting up .mat files for unit tests. - setup_unit_tests.m now calls all the other data-generating scripts in the folder to save updating the ci.yaml each time. --- .github/workflows/ci.yml | 5 + tdms/include/arrays.h | 10 +- tdms/include/cuboid.h | 21 ++++ tdms/include/hdf5_io/hdf5_base.h | 29 +++--- tdms/include/hdf5_io/hdf5_dimension.h | 34 +++++++ tdms/include/hdf5_io/hdf5_reader.h | 83 ++++++++++++--- tdms/include/input_matrices.h | 5 + .../include/output_matrices/output_matrices.h | 2 +- tdms/include/shapes.h | 20 ---- .../simulation_manager/objects_from_infile.h | 2 +- tdms/src/arrays.cpp | 9 -- tdms/src/hdf5_io/hdf5_base.cpp | 25 +++-- tdms/src/hdf5_io/hdf5_dimension.cpp | 23 +++++ tdms/src/hdf5_io/hdf5_reader.cpp | 42 +++++++- tdms/src/input_matrices.cpp | 2 + tdms/src/shapes.cpp | 29 ------ .../objects_from_infile.cpp | 30 ++++-- tdms/tests/include/array_test_class.h | 17 ---- tdms/tests/include/unit_test_utils.h | 29 ++++-- .../array_tests/test_FrequencyVectors.cpp | 96 ------------------ .../unit/hdf5_and_tdms_objects/class_data.mat | Bin 10064 -> 0 bytes .../create_tdms_object_data.m | 25 ----- .../test_hdf5_Cuboid.cpp | 40 ++++++++ .../test_hdf5_FrequencyVector.cpp | 65 ++++++++++++ .../unit/hdf5_io_tests/structure_array.mat | Bin 10744 -> 0 bytes .../hdf5_io_tests/test_hdf5_dimension.cpp | 41 ++++++++ .../unit/hdf5_io_tests/test_hdf5_reader.cpp | 16 +-- .../create_bad_tdms_object_data.m | 20 ++++ .../create_structure_array.m | 2 +- .../create_tdms_object_data.m | 40 ++++++++ .../unit/matlab_benchmark_scripts/readme.md | 11 +- .../setup_unit_tests.m | 7 ++ 32 files changed, 501 insertions(+), 279 deletions(-) create mode 100644 tdms/include/cuboid.h create mode 100644 tdms/include/hdf5_io/hdf5_dimension.h delete mode 100644 tdms/include/shapes.h create mode 100644 tdms/src/hdf5_io/hdf5_dimension.cpp delete mode 100644 tdms/src/shapes.cpp delete mode 100644 tdms/tests/unit/array_tests/test_FrequencyVectors.cpp delete mode 100644 tdms/tests/unit/hdf5_and_tdms_objects/class_data.mat delete mode 100644 tdms/tests/unit/hdf5_and_tdms_objects/create_tdms_object_data.m create mode 100644 tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_Cuboid.cpp create mode 100644 tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_FrequencyVector.cpp delete mode 100644 tdms/tests/unit/hdf5_io_tests/structure_array.mat create mode 100644 tdms/tests/unit/hdf5_io_tests/test_hdf5_dimension.cpp create mode 100644 tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m rename tdms/tests/unit/{hdf5_io_tests => matlab_benchmark_scripts}/create_structure_array.m (89%) create mode 100644 tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m create 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 d493cbe9c..12f7919b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,11 @@ 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 + with: + command: cd('tdms/tests/unit/matlab_benchmark_scripts/'), setup_unit_tests + # ------------------------------------------------------------------------------- # Ubuntu - name: Install dependencies for Ubuntu diff --git a/tdms/include/arrays.h b/tdms/include/arrays.h index 366410b24..0abd57257 100644 --- a/tdms/include/arrays.h +++ b/tdms/include/arrays.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -325,12 +326,9 @@ class FrequencyExtractVector : public Vector { double max(); }; -class FrequencyVectors { -public: - Vector x; - Vector y; - - void initialise(const mxArray *ptr); +struct FrequencyVectors { + std::vector x; + std::vector y; }; /** diff --git a/tdms/include/cuboid.h b/tdms/include/cuboid.h new file mode 100644 index 000000000..8d7c10846 --- /dev/null +++ b/tdms/include/cuboid.h @@ -0,0 +1,21 @@ +#pragma once + +#include "mat_io.h" + +/** + * @brief Defines a cuboid by specifying the first and last Yee cells in each +axial direction that form part of the cube. + * + * @details For example, { 0, 5, 2, 7, 1, 10 } corresponds to the cuboid that +contains all Yee cells indexed (i,j,k) where; + * 0 <= i <= 5, + * 2 <= j <= 7, + * 1 <= k <= 10. + * + * TODO: Check inclusivity of the inequalities above. + */ +struct Cuboid { + int array[6] = {0, 0, 0, 0, 0, 0}; + + int operator[](int index) const { return array[index]; } +}; diff --git a/tdms/include/hdf5_io/hdf5_base.h b/tdms/include/hdf5_io/hdf5_base.h index 865f93eb4..188bb8476 100644 --- a/tdms/include/hdf5_io/hdf5_base.h +++ b/tdms/include/hdf5_io/hdf5_base.h @@ -13,16 +13,7 @@ #include #include "cell_coordinate.h" - -/** - * @brief Convert from a vector of HDF5's hsize_t back to our struct of ints. - * @note Local scope utility function as only this code needs to interact with - * the HDF5 H5Cpp library. - * - * @param dimensions a 1, 2, or 3 element vector of dimensions. - * @return ijk The dimensions in a struct. - */ -ijk to_ijk(const std::vector dimensions); +#include "hdf5_io/hdf5_dimension.h" /** * @brief The base class for HDF5 I/O. @@ -74,12 +65,22 @@ class HDF5Base { /** * @brief Return shape/dimensionality information about the array data stored - * with `name`. + * with `dataname`. * @param dataname The name of the data table. - * @return IJKDimensions The dimensions of the data. + * @return The dimensions of the data. + */ + H5Dimension shape_of(const std::string &dataname) const; + /** + * @brief Return shape/dimensionality information about array data stored + * within a group. + * + * @param group_name The name of the HDF5 Group in which the data array is + * stored. + * @param dataname The name of the data array to check dimensions of. + * @return The dimensions of the data. */ - // IJKDimensions shape_of(const std::string &dataname) const; - std::vector shape_of(const std::string &dataname) const; + H5Dimension shape_of(const std::string &group_name, + const std::string &dataname) const; /** * @brief Checks the file is a valid HDF5 file, and everything is OK. diff --git a/tdms/include/hdf5_io/hdf5_dimension.h b/tdms/include/hdf5_io/hdf5_dimension.h new file mode 100644 index 000000000..a508164db --- /dev/null +++ b/tdms/include/hdf5_io/hdf5_dimension.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include + +class H5Dimension : public std::vector { +public: + H5Dimension() = default; + H5Dimension(const H5::DataSpace &data_space); + + /** + * @brief Whether these dimensions describe an array that is castable to a 1D + * array. + * @details In the event that these dimensions only have one entry, or at + * most one of the entries is greater than 1, the shape described can be cast + * to a 1D-array of length max_dim(). + * + * @return true These dimensions describe (an object castable to) a 1D-array + * @return false Otherwise + */ + bool is_1D() const; + + /** + * @brief Returns the dimension of the greatest extent. + * @details For instances where is_1D() returns true, this conincides with the + * number of elements in the array, and the length of a 1D array necessary to + * hold all the elements. + * + * @return hsize_t + */ + hsize_t max_dim() const { return *std::max_element(begin(), end()); }; +}; diff --git a/tdms/include/hdf5_io/hdf5_reader.h b/tdms/include/hdf5_io/hdf5_reader.h index e107be161..76fe80b89 100644 --- a/tdms/include/hdf5_io/hdf5_reader.h +++ b/tdms/include/hdf5_io/hdf5_reader.h @@ -3,6 +3,7 @@ #include "hdf5_io/hdf5_base.h" #include "arrays.h" +#include "cuboid.h" #include "interface.h" /** @@ -21,13 +22,33 @@ class HDF5Reader : public HDF5Base { HDF5Reader(const std::string &filename) : 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. + * @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 data The buffer into which to write the data. + */ + template + void read_dataset_in_group(const std::string &group, + const std::string &dataset, T *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 + H5::DataSet requested_field = structure_array.openDataSet(dataset); + requested_field.read(data, requested_field.getDataType()); + } + /** * @brief Reads a named dataset from the HDF5 file. * @param dataname The name of the datset to be read. * @param data A pointer to an array of correct size. */ - // template - // void read(const std::string &dataname, T *data) const; template void read(const std::string &dataset_name, T *data) const { spdlog::debug("Reading {} from file: {}", dataset_name, filename_); @@ -41,19 +62,13 @@ class HDF5Reader : public HDF5Base { spdlog::trace("Read successful."); } - template - void read_field_from_struct(const std::string &struct_name, - const std::string &field_name, T *data) const { - spdlog::debug("Reading {} from file: {}", struct_name, filename_); - - // Structs are saved as groups, so we need to fetch the group this struct is - // contained in - H5::Group structure_array = file_->openGroup(struct_name); - // Then fetch the requested data and read it into the buffer provided - H5::DataSet requested_field = structure_array.openDataSet(field_name); - requested_field.read(data, requested_field.getDataType()); - } - + /** + * @brief Reads a 2D-dataset into a Matrix object. + * + * @tparam T C++ datatype of the Matrix object + * @param dataset_name Name of the dataset to read data from + * @param data_location Matrix object buffer + */ template void read(const std::string &dataset_name, Matrix &data_location) const { spdlog::debug("Reading {} from file: {}", dataset_name, filename_); @@ -80,10 +95,48 @@ class HDF5Reader : public HDF5Base { return; } + /** + * @brief Read an InterfaceComponent into the buffer provided. + * + * @param[in] plane The plane {I,J,K}{0,1} to read from the file. + * @param[out] ic InterfaceComponent reference to populate/overwrite. + */ void read(const std::string &plane, InterfaceComponent *ic) const; + /** + * @brief Read an InterfaceComponent from the file. + * + * @param plane The plane {I,J,K}{0,1} to read from the file. + * @return InterfaceComponent corresponding to the requested plane. + */ InterfaceComponent read(const std::string &plane) const { InterfaceComponent ic; read(plane, &ic); return ic; } + + /** + * @brief Read FrequencyVectors into the buffer provided. + * + * @param[out] f_vec FrequencyVectors reference to populate/overwrite. + */ + void read(FrequencyVectors *f_vec) const; + /** + * @brief Read FrequencyVectors from the file. + * + * @return FrequencyVectors object containing the data from the input file. + */ + FrequencyVectors read() const { + FrequencyVectors f_vec; + read(&f_vec); + return f_vec; + } + + /** + * @brief + * + * Cuboid is just the phasorsurface array which is it's own dataset, but needs + * to be offset by -1 b/c indexing things + * @param cube + */ + void read(Cuboid *cube) const; }; diff --git a/tdms/include/input_matrices.h b/tdms/include/input_matrices.h index 92d7ed59f..690e7694b 100644 --- a/tdms/include/input_matrices.h +++ b/tdms/include/input_matrices.h @@ -39,6 +39,11 @@ class InputMatrices { void validate_assigned_pointers(); public: + /*! Name of the input file from which MATLAB objects were extracted. Akward + * middle-child between full removal of MATLAB and the slow removal of class + * dependencies */ + std::string input_filename; + InputMatrices() = default; diff --git a/tdms/include/output_matrices/output_matrices.h b/tdms/include/output_matrices/output_matrices.h index 3c9e07748..ab622b4bc 100644 --- a/tdms/include/output_matrices/output_matrices.h +++ b/tdms/include/output_matrices/output_matrices.h @@ -7,6 +7,7 @@ #include #include "cell_coordinate.h" +#include "cuboid.h" #include "field.h" #include "fieldsample.h" #include "grid_labels.h" @@ -14,7 +15,6 @@ #include "matrix.h" #include "output_matrices/id_variables.h" #include "output_matrices/output_matrix_pointers.h" -#include "shapes.h" #include "simulation_parameters.h" #include "surface_phasors.h" #include "vertex_phasors.h" diff --git a/tdms/include/shapes.h b/tdms/include/shapes.h deleted file mode 100644 index f624b69bd..000000000 --- a/tdms/include/shapes.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "mat_io.h" - -class Cuboid { -private: - /* The indices of the first and last Yee cells in each axial direction that - are encompassed by the cuboid. For example, { 0, 5, 2, 7, 1, 10 } corresponds - to the cuboid that contains all Yee cells indexed (i,j,k) where 0 <= i <= 5, 2 - <= j <= 7, 1 <= k <= 10. - - TODO: Check inclusivity of the inequalities above. - */ - int array[6] = {0, 0, 0, 0, 0, 6}; - -public: - void initialise(const mxArray *ptr, int J_tot); - - inline int operator[](int value) const { return array[value]; }; -}; diff --git a/tdms/include/simulation_manager/objects_from_infile.h b/tdms/include/simulation_manager/objects_from_infile.h index 0a186c27c..d26f31041 100644 --- a/tdms/include/simulation_manager/objects_from_infile.h +++ b/tdms/include/simulation_manager/objects_from_infile.h @@ -11,6 +11,7 @@ #include "arrays.h" #include "cell_coordinate.h" +#include "cuboid.h" #include "field.h" #include "fieldsample.h" #include "globals.h" @@ -18,7 +19,6 @@ #include "input_flags.h" #include "input_matrices.h" #include "interface.h" -#include "shapes.h" #include "simulation_parameters.h" #include "source.h" #include "vertex_phasors.h" diff --git a/tdms/src/arrays.cpp b/tdms/src/arrays.cpp index 7d92d539e..e34fade35 100644 --- a/tdms/src/arrays.cpp +++ b/tdms/src/arrays.cpp @@ -236,15 +236,6 @@ double FrequencyExtractVector::max() { return tmp; } -void FrequencyVectors::initialise(const mxArray *ptr) { - - if (mxIsEmpty(ptr)) { return; } - - assert_is_struct_with_n_fields(ptr, 2, "f_vec"); - x = Vector(ptr_to_vector_in(ptr, "fx_vec", "f_vec")); - y = Vector(ptr_to_vector_in(ptr, "fy_vec", "f_vec")); -} - void Pupil::initialise(const mxArray *ptr, int n_rows, int n_cols) { if (mxIsEmpty(ptr)) { return; } diff --git a/tdms/src/hdf5_io/hdf5_base.cpp b/tdms/src/hdf5_io/hdf5_base.cpp index 7f0aeabc4..d07a957e1 100644 --- a/tdms/src/hdf5_io/hdf5_base.cpp +++ b/tdms/src/hdf5_io/hdf5_base.cpp @@ -45,20 +45,19 @@ void HDF5Base::ls() const { return; } -// IJKDimensions HDF5Base::shape_of(const std::string &dataname) const { -// return to_ijk(dimensions); -vector HDF5Base::shape_of(const string &dataname) const { - SPDLOG_DEBUG("shape_of"); - - // get the dataset and dataspace (contains dimensionality info) - H5::DataSet dataset = file_->openDataSet(dataname); - H5::DataSpace dataspace = dataset.getSpace(); +H5Dimension HDF5Base::shape_of(const string &dataname) const { + // Get the dataspace (contains dimensionality info) + H5::DataSpace dataspace = file_->openDataSet(dataname).getSpace(); + return H5Dimension(dataspace); +} - // need the rank in order to declare the vector size - int rank = dataspace.getSimpleExtentNdims(); - vector dimensions(rank); - dataspace.getSimpleExtentDims(dimensions.data(), nullptr); - return dimensions; +H5Dimension HDF5Base::shape_of(const string &group_name, + const string &dataname) const { + // Open the group that contains the dataset + H5::Group group = file_->openGroup(group_name); + // Get the DataSpace for the DataSet within the group + H5::DataSpace dataspace = group.openDataSet(dataname).getSpace(); + return H5Dimension(dataspace); } bool HDF5Base::is_ok() const { diff --git a/tdms/src/hdf5_io/hdf5_dimension.cpp b/tdms/src/hdf5_io/hdf5_dimension.cpp new file mode 100644 index 000000000..8ba6aa45b --- /dev/null +++ b/tdms/src/hdf5_io/hdf5_dimension.cpp @@ -0,0 +1,23 @@ +#include "hdf5_io/hdf5_dimension.h" + +#include + +using namespace std; + +H5Dimension::H5Dimension(const H5::DataSpace &data_space) { + // Fetch rank to declare the vector size + int rank = data_space.getSimpleExtentNdims(); + // Resize to the correct rank then populate entries with the data + resize(rank); + data_space.getSimpleExtentDims(data(), nullptr); +} + +bool H5Dimension::is_1D() const { + int n_non_trivial_dimensions = 0; + if (size() != 1) { + for (hsize_t dim : *this) { + if (dim > 1) { n_non_trivial_dimensions++; } + } + } + return n_non_trivial_dimensions <= 1; +} diff --git a/tdms/src/hdf5_io/hdf5_reader.cpp b/tdms/src/hdf5_io/hdf5_reader.cpp index d002bc0f5..ea48a7717 100644 --- a/tdms/src/hdf5_io/hdf5_reader.cpp +++ b/tdms/src/hdf5_io/hdf5_reader.cpp @@ -1,14 +1,54 @@ #include "hdf5_io/hdf5_reader.h" +#include "hdf5_io/hdf5_dimension.h" + +#include +#include using namespace std; void HDF5Reader::read(const string &plane, InterfaceComponent *ic) const { // Read the InterfaceComponent in as a 2-element double array double read_buffer[2]; - read_field_from_struct("interface", plane, read_buffer); + read_dataset_in_group("interface", plane, read_buffer); // The index that is read in should have 1 subtracted from it, to account for // MATLAB indexing ic->index = max((int) read_buffer[0] - 1, 0); // The apply flag should be cast from the double that is read in ic->apply = (bool) read_buffer[1]; } + +void HDF5Reader::read(FrequencyVectors *f_vec) const { + // Allocate memory in f_vec + H5Dimension x_dims = shape_of("f_vec", "fx_vec"); + H5Dimension y_dims = shape_of("f_vec", "fy_vec"); + // Check that we have one dimensional arrays + if (!x_dims.is_1D() || !y_dims.is_1D()) { + throw runtime_error("f_vec members are not 1D arrays!"); + } + // Allocate memory - resize() must be used over reserve() to ensure enough + // buffer when we read in, _and_ that size() correctly returns the number of + // elements in the buffer. + f_vec->x.resize(x_dims.max_dim()); + f_vec->y.resize(y_dims.max_dim()); + // Now read the data into the vectors + read_dataset_in_group("f_vec", "fx_vec", f_vec->x.data()); + read_dataset_in_group("f_vec", "fy_vec", f_vec->y.data()); +} + +void HDF5Reader::read(Cuboid *cube) const { + string cuboid_dataset = "phasorsurface"; + // Check that we are reading in a 1D array with 6 elements + H5Dimension cuboid_dims(file_->openDataSet(cuboid_dataset).getSpace()); + if (!(cuboid_dims.is_1D() && cuboid_dims.max_dim() == 6)) { + throw runtime_error( + "Error: phasorsurface is not a 1D vector of 6 elements"); + } + // Read buffer then adjust for indexing offset between MATLAB and C++ + // NOTE: Buffer is saved as doubles in .mat file, but we want to read as + // integers here. + double intermediate_buffer[6]; + read(cuboid_dataset, intermediate_buffer); + for (int i = 0; i < 6; i++) { + cube->array[i] = (int) intermediate_buffer[i] - 1; + } +} diff --git a/tdms/src/input_matrices.cpp b/tdms/src/input_matrices.cpp index 77c734be6..8085d621d 100644 --- a/tdms/src/input_matrices.cpp +++ b/tdms/src/input_matrices.cpp @@ -24,6 +24,7 @@ int InputMatrices::index_from_matrix_name(const string &matrix_name) { } void InputMatrices::set_from_input_file(const char *mat_filename) { + input_filename = std::string(mat_filename); MatrixCollection infile_expected(matrixnames_input_with_grid); MatFileMatrixCollection infile_contains(mat_filename); spdlog::info("Input file: " + string(mat_filename) + @@ -51,6 +52,7 @@ void InputMatrices::set_from_input_file(const char *mat_filename) { } void InputMatrices::set_from_input_file(const char *mat_filename, const char *gridfile) { + input_filename = std::string(mat_filename); MatrixCollection infile_expected(matrixnames_infile); MatFileMatrixCollection infile_contains(mat_filename); spdlog::info("Input file: " + string(mat_filename) + diff --git a/tdms/src/shapes.cpp b/tdms/src/shapes.cpp deleted file mode 100644 index 43f296daa..000000000 --- a/tdms/src/shapes.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "shapes.h" - -#include -#include - -using namespace std; - -void Cuboid::initialise(const mxArray *ptr, int J_tot) { - - auto ndims = mxGetNumberOfDimensions(ptr); - auto dims = mxGetDimensions((mxArray *) ptr); - - if (ndims != 2) { - throw runtime_error("expected phasorsurface to be a vector of length 6"); - } - if (dims[0] != 1 || dims[1] != 6) { - throw runtime_error("expected phasorsurface to be a vector of length 6"); - } - - for (int i = 0; i < 6; i++) { - array[i] = max((int) *(mxGetPr(ptr) + i) - - 1,// Change from MATLAB -> C indexing - 0); - } - if (J_tot == 0 && array[2] != array[3]) { - throw runtime_error( - "When doing a 2D simulation, J0 should equal J1 in phasorsurface."); - } -} diff --git a/tdms/src/simulation_manager/objects_from_infile.cpp b/tdms/src/simulation_manager/objects_from_infile.cpp index f71f76db7..0c508f816 100644 --- a/tdms/src/simulation_manager/objects_from_infile.cpp +++ b/tdms/src/simulation_manager/objects_from_infile.cpp @@ -3,10 +3,9 @@ #include #include -// For ptr_to_vector_in, ptr_to_vector_or_empty_in, int_cast_from_double_in -#include "matlabio.h" -// for init_grid_arrays #include "array_init.h" +#include "hdf5_io/hdf5_reader.h" +#include "matlabio.h" using tdms_math_constants::DCPI; using namespace tdms_flags; @@ -18,12 +17,6 @@ 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 - I0(matrices_from_input_file["interface"], "I0"), // get the interface(s) - I1(matrices_from_input_file["interface"], "I1"), - J0(matrices_from_input_file["interface"], "J0"), - J1(matrices_from_input_file["interface"], "J1"), - K0(matrices_from_input_file["interface"], "K0"), - K1(matrices_from_input_file["interface"], "K1"), matched_layer( matrices_from_input_file["dispersive_aux"]),// get dispersive_aux Ei(matrices_from_input_file["tdfield"]) // get tdfield @@ -39,6 +32,17 @@ IndependentObjectsFromInfile::IndependentObjectsFromInfile( E_s.set_preferred_interpolation_methods(i_method); H_s.set_preferred_interpolation_methods(i_method); + // HDF5Reader to extract data from the input file + HDF5Reader INPUT_FILE(matrices_from_input_file.input_filename); + + // Read the interface components + I0 = INPUT_FILE.read("I0"); + I1 = INPUT_FILE.read("I1"); + J0 = INPUT_FILE.read("J0"); + J1 = INPUT_FILE.read("J1"); + K0 = INPUT_FILE.read("K0"); + K1 = INPUT_FILE.read("K1"); + // unpack the parameters for this simulation params.unpack_from_input_matrices(matrices_from_input_file); @@ -66,7 +70,11 @@ IndependentObjectsFromInfile::IndependentObjectsFromInfile( // Get phasorsurface cuboid = Cuboid(); if (params.exphasorssurface && params.run_mode == RunMode::complete) { - cuboid.initialise(matrices_from_input_file["phasorsurface"], IJK_tot.j); + INPUT_FILE.read(&cuboid); + if (IJK_tot.j == 0 && cuboid[2] != cuboid[3]) { + throw std::runtime_error("In a 2D simulation, J0 should equal J1 in " + "phasorsurface."); + } } // Get conductive_aux, and setup with pointers @@ -88,7 +96,7 @@ IndependentObjectsFromInfile::IndependentObjectsFromInfile( D_tilde = DTilde(); // if exdetintegral is flagged, setup pupil, D_tilde, and f_vec accordingly if (params.exdetintegral) { - f_vec.initialise(matrices_from_input_file["f_vec"]); + f_vec = INPUT_FILE.read(); pupil.initialise(matrices_from_input_file["Pupil"], f_vec.x.size(), f_vec.y.size()); D_tilde.initialise(matrices_from_input_file["D_tilde"], f_vec.x.size(), diff --git a/tdms/tests/include/array_test_class.h b/tdms/tests/include/array_test_class.h index 4534682a4..c1dfa391c 100644 --- a/tdms/tests/include/array_test_class.h +++ b/tdms/tests/include/array_test_class.h @@ -110,23 +110,6 @@ class FieldSampleTest : public AbstractArrayTest { std::string get_class_name() override { return "FieldSample"; } }; -// Test methods check the performance of initialise, as this is the de-facto -// constructor -class FrequencyVectorsTest : public AbstractArrayTest { -private: - const int n_fields = 2; - const char *fieldnames[2] = {"fx_vec", "fy_vec"}; - - void test_empty_construction() override; - void test_wrong_input_type() override; - void test_incorrect_number_of_fields() override; - void test_correct_construction() override; - void test_initialise_method() override; - -public: - std::string get_class_name() override { return "FrequencyVectors"; } -}; - /** @brief Unit tests for FullFieldSnapshot */ class FullFieldSnapshotTest : public AbstractArrayTest { private: diff --git a/tdms/tests/include/unit_test_utils.h b/tdms/tests/include/unit_test_utils.h index 74e1c6125..70084fcd0 100644 --- a/tdms/tests/include/unit_test_utils.h +++ b/tdms/tests/include/unit_test_utils.h @@ -16,13 +16,20 @@ using tdms_math_constants::DCPI; namespace tdms_unit_test_data { #ifdef CMAKE_SOURCE_DIR -inline std::string - tdms_object_data(std::string(CMAKE_SOURCE_DIR) + - "/tests/unit/hdf5_and_tdms_objects/class_data.mat"); +inline std::string tdms_object_data(std::string(CMAKE_SOURCE_DIR) + + "/tests/unit/matlab_benchmark_scripts/" + "matlab_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_object_data(std::filesystem::current_path() / - "../tests/unit/hdf5_and_tdms_objects/class_data.mat"); + 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, @@ -38,13 +45,13 @@ double_22: [0.25, 0.5; 0.75, 1.], double complex_22: [0., -i; i, 0], complex */ #ifdef CMAKE_SOURCE_DIR -inline std::string - struct_testdata(std::string(CMAKE_SOURCE_DIR) + - "/tests/unit/hdf5_io_tests/structure_array.mat"); +inline std::string struct_testdata(std::string(CMAKE_SOURCE_DIR) + + "/tests/unit/matlab_benchmark_scripts/" + "matlab_data/structure_array.mat"); #else -inline std::string - struct_testdata(std::filesystem::current_path() / - "../tests/unit/hdf5_io_tests/structure_array.mat"); +inline std::string struct_testdata(std::filesystem::current_path() / + "../tests/unit/matlab_benchmark_scripts/" + "matlab_data/structure_array.mat"); #endif }// namespace tdms_unit_test_data diff --git a/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp deleted file mode 100644 index 2587f67a2..000000000 --- a/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @file test_FrequencyVectors.cpp - * @author William Graham (ccaegra@ucl.ac.uk) - * @brief Tests for the FrequencyVectors class and its subclasses - */ -#include -#include - -#include "array_test_class.h" -#include "arrays.h" -#include "unit_test_utils.h" - -using namespace std; -using tdms_tests::TOLERANCE; - -void FrequencyVectorsTest::test_empty_construction() { - // initialise() method should exit without assignment if we pass in a pointer - // to an empty array (regardless of whether this is a struct or not) - FrequencyVectors fv; - dimensions_2d[0] = 0; - create_numeric_array(2, dimensions_2d, mxUINT8_CLASS); - // attempt assignment - fv.initialise(matlab_input); - // should still be unassigned vectors - bool not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); - REQUIRE(not_assigned); -} - -void FrequencyVectorsTest::test_wrong_input_type() { - FrequencyVectors fv; - // throw error if we attempt to provide a non-empty, non-struct array - dimensions_2d[0] = 2; - dimensions_2d[1] = 3; - create_numeric_array(2, dimensions_2d, mxUINT8_CLASS); - REQUIRE_THROWS_AS(fv.initialise(matlab_input), runtime_error); -} - -void FrequencyVectorsTest::test_incorrect_number_of_fields() { - // assignment will throw error if we attempt to provide a struct array that - // doesn't have two fields - FrequencyVectors fv; - SECTION("Struct with too many inputs") { - const char *too_many_names[3] = {"field1", "field2", "field3"}; - create_1by1_struct(3, too_many_names); - CHECK_THROWS_AS(fv.initialise(matlab_input), runtime_error); - } - SECTION("Struct with too few inputs") { - create_1by1_struct(n_fields - 1, fieldnames); - CHECK_THROWS_AS(fv.initialise(matlab_input), runtime_error); - } -} - -void FrequencyVectorsTest::test_correct_construction() { - FrequencyVectors fv; - // members should start unassigned - bool not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); - REQUIRE(not_assigned); -} - -void FrequencyVectorsTest::test_initialise_method() { - FrequencyVectors fv; - const int field_array_dimensions[2] = {1, n_numeric_elements}; - // create the struct - create_1by1_struct(n_fields, fieldnames); - // create the data for the fields of our struct - mxArray *field_array_ptrs[2]; - for (int i = 0; i < 2; i++) { - field_array_ptrs[i] = mxCreateNumericArray( - 2, (const mwSize *) field_array_dimensions, mxDOUBLE_CLASS, mxREAL); - mxDouble *where_to_place_data = mxGetPr(field_array_ptrs[i]); - // 0th field, fx_vec[i], will be 1/(i+1) - // 1st field, fy_vec[i], will be -1/(i+1) - for (int j = 0; j < n_numeric_elements; j++) { - where_to_place_data[j] = pow(-1., (double) i) / ((double) (j + 1)); - } - mxSetField(matlab_input, 0, fieldnames[i], field_array_ptrs[i]); - } - // attempt to create a vector from this struct - REQUIRE_NOTHROW(fv.initialise(matlab_input)); - // check that we actually assigned values to the Vectors under the hood - bool not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); - bool expected_size = (fv.x.size() == n_numeric_elements && - fv.y.size() == n_numeric_elements); - bool assigned_and_correct_size = ((!not_assigned) && expected_size); - REQUIRE(assigned_and_correct_size); - // and the values themselves are what we expect - bool values_are_correct = true; - for (int i = 0; i < n_numeric_elements; i++) { - values_are_correct = values_are_correct && - (abs(fv.x[i] - 1. / ((double) (i + 1))) < TOLERANCE) && - (abs(fv.y[i] + 1. / ((double) (i + 1))) < TOLERANCE); - } - REQUIRE(values_are_correct); -} - -TEST_CASE("FrequencyVectors") { FrequencyVectorsTest().run_all_class_tests(); } diff --git a/tdms/tests/unit/hdf5_and_tdms_objects/class_data.mat b/tdms/tests/unit/hdf5_and_tdms_objects/class_data.mat deleted file mode 100644 index 37d7be667656278a38ca573010b88bdc09be48a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10064 zcmeHL%~Bdc5bnjljiNzgsz^>#HJRmjar4@G)uMmNlI<(>>j2U5;JPG?mA7ksA|=edZ&5TIag|4#p9{uVk)_y=3;a6Dzo-B zsTzf2_ry{0*;q`?s%`{Aq0Ou`4Zj-%KI(TiI{bXto87jdA0o7n5V0$!+>7fnNdQOO zu$W&k|HA$j;2%Jg_2WFg-!8wuz7E<_<`d3t@MC~lwkQn$uMo(rr*rtghy2uv`8SR; z7NJ~iy7iJ%a9J@`cT0^aj4YxhF}x+W^p5C^-w4O=;Uo=k$>a2j-_ubtjNNixWA?BR z@W-#j;RT;-^0b>AAe(X0Y_Yt>@;1xcEK6J~8NkO6cK6mS2(`}N=^-)@zZ}b_k4P`) ztLnRMmtpzXw_e|L4^|AyV}AU4odfpBb(|k8VCKQPBH%vm{mk&eIQ z7T8`Mi|~LJp9q#k3Q42F`*fbe9$X)t2`k>r13XU13VDt;kAwEzXo9`bIPrSF7zR4| zEEt8b?$ehJegAmxo}4z%(H}>l716BJ79D7xw!hu>FI1dH1O1=q|HrK{018gERxKBt zijpb1uanM6M2YC9?|rb^upS89jI(LagTqBR`g!uBj5S__r`V} zy2=$UWGgRagL4Ey&bO>;QT6e0oozS{a$3@a^L4Nr&TA({U7H>5_|a` zAlGrx%lCoK=S4sHUVv?+qaJkvS(#shfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1iB&c E8woubM*si- diff --git a/tdms/tests/unit/hdf5_and_tdms_objects/create_tdms_object_data.m b/tdms/tests/unit/hdf5_and_tdms_objects/create_tdms_object_data.m deleted file mode 100644 index 992d46c25..000000000 --- a/tdms/tests/unit/hdf5_and_tdms_objects/create_tdms_object_data.m +++ /dev/null @@ -1,25 +0,0 @@ -%% This script creates the class_data.mat file for use in the hdf5 tests for reading in TDMS objects. -close all; -clear; - -% % interface % interface is a struct with fields { - I, J, K -} {0, 1}, corresponding to the planes at which a source field is introduced.% - Each field is a 1 - - by - 2 array of doubles; -the first element being the % - {I, J, K} index of the Yee cell in which the particular plane lies.The % - second element is cast to a bool and indicates whether any boundary % - conditions are to be applied on said plane.interface = struct(); -interface.I0 = [1 0]; -% I0 in the I = 1 plane, no source condition interface.I1 = [4 1]; -% I1 in the I = 4 plane, source condition applied interface.J0 = [2 0]; -% J0 in the J = 2 plane, no source condition interface.J1 = [5 0]; -% J1 in the J = 5 plane, no source condition interface.K0 = [3 1]; -% K0 in the K = 3 plane, source condition applied interface.K1 = [6 1]; %K1 in the K=6 plane, source condition applied - -%% 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("class_data.mat", "-v7.3"); diff --git a/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_Cuboid.cpp b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_Cuboid.cpp new file mode 100644 index 000000000..8260afd1f --- /dev/null +++ b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_Cuboid.cpp @@ -0,0 +1,40 @@ +#include "hdf5_io/hdf5_reader.h" + +// external +#include +#include +#include + +#include + +#include "unit_test_utils.h" + +using tdms_unit_test_data::tdms_object_data, + tdms_unit_test_data::tdms_bad_object_data; + +/** + * @brief Test that a Cuboid object can be successfully read in. + * + * The (correct) Cuboid we have prepared is just the array {1, 4, 2, 5, 3, 6}. + * The bad data provides us with a column vector of 7 elements. + */ +TEST_CASE("HDF5: Read Cuboid") { + Cuboid cube; + + SECTION("Read into existing object") { + HDF5Reader MATFile(tdms_object_data); + MATFile.read(&cube); + // Check expected values, noting the -1 offset that is applied because of + // MATLAB indexing + bool expected_values_recieved = cube[0] == 0 && cube[1] == 3 && + cube[2] == 1 && cube[3] == 4 && + cube[4] == 2 && cube[5] == 5; + REQUIRE(expected_values_recieved); + } + + SECTION("Throw error if too many elements provided") { + HDF5Reader MATFile(tdms_bad_object_data); + // Error should be thrown due to incorrect dimensions + REQUIRE_THROWS_AS(MATFile.read(&cube), std::runtime_error); + } +} diff --git a/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_FrequencyVector.cpp b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_FrequencyVector.cpp new file mode 100644 index 000000000..f6664a618 --- /dev/null +++ b/tdms/tests/unit/hdf5_and_tdms_objects/test_hdf5_FrequencyVector.cpp @@ -0,0 +1,65 @@ +#include "hdf5_io/hdf5_reader.h" + +#include + +// external +#include +#include +#include + +// tdms +#include "unit_test_utils.h" + +using Catch::Approx; +using tdms_unit_test_data::tdms_object_data, + tdms_unit_test_data::tdms_bad_object_data; + +inline int EXPECTED_VEC_SIZE = 4; + +/** + * @brief Test the reading of frequency vectors from the input file, and failure + * cases when they are of the incorrect dimension. + * + * f_vec is a group whose data consists of: + * fx_vec: double[4] ROW vector whose entries are 1/4, 2/4, 3/4, 4/4 + * fy_vec: double[4] COL vector whose entries are -1/4, -2/4, -3/4, -4/4 + * + * f_vec_bad is a group whose data consists of: + * fx_vec: double[2,2] of zero entries + * fy_vec: double[1,2] of zero entries. + */ +TEST_CASE("HDF5: Read FrequencyVector") { + // FrequencyVectors are automatically read from the structure array "f_vec", + // and consist of real-valued arrays of the same length "fx_vec" and "fy_vec" + FrequencyVectors f_vec; + + SECTION("Correct data") { + HDF5Reader MATFile(tdms_object_data); + + SECTION("Read into existing FrequencyVectors object") { + MATFile.read(&f_vec); + } + SECTION("Return FrequencyVectors object") { f_vec = MATFile.read(); } + + // Confirm expected sizes + CHECK(f_vec.x.size() == EXPECTED_VEC_SIZE); + CHECK(f_vec.y.size() == EXPECTED_VEC_SIZE); + // Confirm correct assignment of entries + bool x_entries_correct = true; + bool y_entries_correct = true; + for (int i = 0; i < EXPECTED_VEC_SIZE; i++) { + x_entries_correct = + x_entries_correct && (f_vec.x[i] == Approx((i + 1) / 4.)); + y_entries_correct = + y_entries_correct && (f_vec.y[i] == Approx(-(i + 1) / 4.)); + } + REQUIRE(x_entries_correct); + REQUIRE(y_entries_correct); + } + + // Bad object data provides a 2D array for fx_vec component + SECTION("Incorrect sizes") { + HDF5Reader MATFile(tdms_bad_object_data); + REQUIRE_THROWS_AS(MATFile.read(&f_vec), std::runtime_error); + } +} diff --git a/tdms/tests/unit/hdf5_io_tests/structure_array.mat b/tdms/tests/unit/hdf5_io_tests/structure_array.mat deleted file mode 100644 index ee5d259d32525b563bcac6be982ad4ca9abbaa3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10744 zcmeHML2pw>5T0j~#s!BuX=!OsD{)CBnmSGbQB(=L5OCE5tTqR3=3zf$OV56m?MNUl zInrB?y;SP4$NYm{Ir0-Ya^%R3&g{%M&WoRuI0q2fwesfe?9A-$x3e?*Hru70tjjPeHS#Pj>neCc>cPw*|IMlY zia**xp{?Wmw>ifh7Ejo(6Z^CfQ2*VRX?2DE^CiEiUoC#a^;goKg}i<9U=A|)!z7LQ zx>^hUexDjh{WJVi|5m@JHY&g0e{jcx)G+=o&N5%+D^l-%&U}rpCVg10EcK@NJ-42p zXHp)E<9A#f$Xq&%^Z7y0&6E((IG}kfC$Rh}`%h8wxx`l@o)WLr^AlfAS{O@1uS>Oyz@hppN63>Emckqn# z$qJ8%d*yfK0qPv+C&j}rhBg3dekba*YJO<6i}B;+I7ujRc@tX?VLNgR0_tZOSvU_C zt%kVEFi*wTRqJW=CwiYZw`47fuzCqr-%I{E>VLeR>EX(~y+s`!^}9TeC3 z1M)Z3o5db7P+qH)cFLT0NB!)__`!2H9^0+t;aTxs^z(VChfNY)S(Ub7&_H{qcP}sYE8dK9cH0O1o;c2`JvSrRO>-4s^gZlC5Mj6b1=59 zb~JxYyKfc8d;*TzPZAx)k!5&C9mSFPX`Cs^O%t!^=&s`kqbHQ*&WxYhJ1c%gG305! zPBTM4AJqROtm9WUmpK3AcowaJH2eg3rkT1-vko+N#cdZol};$r;-{xAJDHIO=c8&T zqHWM><4SW8e$x*dmVw`YtB$F#BE?TFUNqSnRm`T}0>lBaZ^F`~VOBeiFC*6CuA8gr?aGJMcLpJEKG%4QbzLjeX zh{qp|=hP<^jYk^Sb-o&(H15;*sc5`XefKyjUD#YXIh$^Qp=?>JV@U dI0PI54grUNL%<>65O4@M1RMemflm>E{{bhPjR^n% diff --git a/tdms/tests/unit/hdf5_io_tests/test_hdf5_dimension.cpp b/tdms/tests/unit/hdf5_io_tests/test_hdf5_dimension.cpp new file mode 100644 index 000000000..6babc7b27 --- /dev/null +++ b/tdms/tests/unit/hdf5_io_tests/test_hdf5_dimension.cpp @@ -0,0 +1,41 @@ +/** + * @file test_hdf5_dimension.cpp + * @author William Graham + * @brief Unit tests for the H5Dimension object + */ +#include "hdf5_io/hdf5_dimension.h" +#include "hdf5_io/hdf5_reader.h" + +#include + +#include + +#include "unit_test_utils.h" + +using namespace std; +using tdms_unit_test_data::struct_testdata; + +TEST_CASE("Fetch dimensions correctly") { + HDF5Reader MATFile(struct_testdata); + + SECTION("2D array") { + H5Dimension two_by_two = MATFile.shape_of("example_struct", "double_22"); + bool two_by_two_dimensions_read_in = + (two_by_two == vector{2, 2}) && (!two_by_two.is_1D()); + REQUIRE(two_by_two_dimensions_read_in); + } + + SECTION("3D array") { + H5Dimension three_four_five = + MATFile.shape_of("example_struct", "uint_345"); + bool three_four_five_read_in = + (three_four_five == vector{3, 4, 5}) && + (!three_four_five.is_1D()); + } + + SECTION("1D char array") { + H5Dimension tdms_dims = MATFile.shape_of("example_struct", "string"); + bool tdms_dims_correct = (tdms_dims.is_1D()) && (tdms_dims.max_dim() == 4); + REQUIRE(tdms_dims_correct); + } +} 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 513790319..9b887f899 100644 --- a/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp +++ b/tdms/tests/unit/hdf5_io_tests/test_hdf5_reader.cpp @@ -27,10 +27,10 @@ TEST_CASE("Read from a MATLAB struct") { double one_half = 0., unity = 0.; bool logical_read = false; // Read values - MATFile.read_field_from_struct("example_struct", "double_half", &one_half); - MATFile.read_field_from_struct("example_struct", "double_no_decimal", - &unity); - MATFile.read_field_from_struct("example_struct", "boolean", &logical_read); + MATFile.read_dataset_in_group("example_struct", "double_half", &one_half); + MATFile.read_dataset_in_group("example_struct", "double_no_decimal", + &unity); + MATFile.read_dataset_in_group("example_struct", "boolean", &logical_read); // Validate read in data REQUIRE(one_half == Catch::Approx(0.5)); REQUIRE(unity == Catch::Approx(1.)); @@ -44,15 +44,15 @@ TEST_CASE("Read from a MATLAB struct") { // we need to convert manually... { uint16_t read_uints16[4]; - MATFile.read_field_from_struct("example_struct", "string", read_uints16); + MATFile.read_dataset_in_group("example_struct", "string", read_uints16); string tdms = uint16s_to_string(read_uints16, 4); REQUIRE(tdms == "tdms"); } // The uint 3*4*5 uint matrix contains only 1s { vector uint_matrix(3 * 4 * 5, 0); - MATFile.read_field_from_struct("example_struct", "uint_345", - uint_matrix.data()); + MATFile.read_dataset_in_group("example_struct", "uint_345", + uint_matrix.data()); bool all_values_unity = true; for (uint8_t &value : uint_matrix) { if (value != 1) { all_values_unity = false; } @@ -62,7 +62,7 @@ TEST_CASE("Read from a MATLAB struct") { // The double 2*2 matrix contains 0.25, 0.5, 0.75, 1. { double two_by_two[4]; - MATFile.read_field_from_struct("example_struct", "double_22", two_by_two); + MATFile.read_dataset_in_group("example_struct", "double_22", two_by_two); REQUIRE(two_by_two[0] == Catch::Approx(0.25)); REQUIRE(two_by_two[1] == Catch::Approx(0.75)); REQUIRE(two_by_two[2] == Catch::Approx(0.5)); diff --git a/tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m b/tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m new file mode 100644 index 000000000..53ba6f504 --- /dev/null +++ b/tdms/tests/unit/matlab_benchmark_scripts/create_bad_tdms_object_data.m @@ -0,0 +1,20 @@ +%% This script creates the bad_class_data.mat file for use in the hdf5 tests for reading in TDMS objects. +% These variables are used to check failure-cases for when the reader methods should detect an error. +close all; +clear; + +%% f_vec +% f_vec is a struct with two fields, fx_vec and fy_vec. +f_vec = struct(); +f_vec.fx_vec = zeros(2,2); % Non-2D elements are not expected! +f_vec.fy_vec = zeros(2,1); % This is permitted, but the former field should error + +%% phasorsurface +% 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; 7;]; % 7 elements should throw an error + +%% 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/bad_class_data.mat", "-v7.3"); diff --git a/tdms/tests/unit/hdf5_io_tests/create_structure_array.m b/tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m similarity index 89% rename from tdms/tests/unit/hdf5_io_tests/create_structure_array.m rename to tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m index d4f3e5128..2bdcf8559 100644 --- a/tdms/tests/unit/hdf5_io_tests/create_structure_array.m +++ b/tdms/tests/unit/matlab_benchmark_scripts/create_structure_array.m @@ -16,4 +16,4 @@ % Save the files to the expected filename for the unit tests to read the % data back in. -save("structure_array.mat", "example_struct", "-v7.3"); +save("matlab_data/structure_array.mat", "example_struct", "-v7.3"); diff --git a/tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m b/tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m new file mode 100644 index 000000000..693f01603 --- /dev/null +++ b/tdms/tests/unit/matlab_benchmark_scripts/create_tdms_object_data.m @@ -0,0 +1,40 @@ +%% This script creates the class_data.mat file for use in the hdf5 tests for reading in TDMS objects. +close all; +clear; + +%% interface +% interface is a struct with fields {I, J, K} {0, 1}, corresponding to the planes at which a source field is introduced. +% Each field is a 1 - by - 2 array of doubles; the first element being the +% {I, J, K} index of the Yee cell in which the particular plane lies. +% The second element is cast to a bool and indicates whether any boundary +% conditions are to be applied on said plane. +interface = struct(); +interface.I0 = [1 0]; % I0 in the I = 1 plane, no source condition +interface.I1 = [4 1]; % I1 in the I = 4 plane, source condition applied +interface.J0 = [2 0]; % J0 in the J = 2 plane, no source condition +interface.J1 = [5 0]; % J1 in the J = 5 plane, no source condition +interface.K0 = [3 1]; % K0 in the K = 3 plane, source condition applied +interface.K1 = [6 1]; %K1 in the K=6 plane, source condition applied + +%% f_vec & f_vec_bad +% f_vec is a struct with two fields, fx_vec and fy_vec. +% These are each 1D vectors of doubles, usually they have the same length +% however for our testing purposes we will make them different lengths. +f_vec = struct(); +f_vec.fx_vec = [0.25 0.5 0.75 1.]; % Row vector w/ 4 elements +f_vec.fy_vec = [-0.25; -0.5; -0.75; -1.]; % Col vector w/ 4 elements + +% f_vec_bad will be used as the failure case in our tests. +f_vec_bad = struct(); +f_vec_bad.fx_vec = zeros(2,2); % Non-2D elements are not expected! +f_vec_bad.fy_vec = zeros(2,1); % This is permitted, but the former field should error + +%% phasorsurface +% 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]; + +%% 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"); diff --git a/tdms/tests/unit/matlab_benchmark_scripts/readme.md b/tdms/tests/unit/matlab_benchmark_scripts/readme.md index 6f2b925bd..98b86290c 100644 --- a/tdms/tests/unit/matlab_benchmark_scripts/readme.md +++ b/tdms/tests/unit/matlab_benchmark_scripts/readme.md @@ -1,6 +1,15 @@ # **`MATLAB` Benchmarking Scripts** -The directory `matlab_benchmark_scripts` contains scripts which perform bandlimited interpolation (BLi) using `MATLAB`'s `interp` function. +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. diff --git a/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m b/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m new file mode 100644 index 000000000..4700184f8 --- /dev/null +++ b/tdms/tests/unit/matlab_benchmark_scripts/setup_unit_tests.m @@ -0,0 +1,7 @@ +if ~exist('matlab_data', 'dir') + mkdir('matlab_data'); +end + +create_structure_array; +create_tdms_object_data; +create_bad_tdms_object_data;