diff --git a/tdms/CMakeLists.txt b/tdms/CMakeLists.txt index c1a01b4d2..0bf6dad4a 100644 --- a/tdms/CMakeLists.txt +++ b/tdms/CMakeLists.txt @@ -136,19 +136,9 @@ install(TARGETS tdms) if (BUILD_TESTING) enable_testing() - add_executable(tdms_tests - tests/unit/test_fields.cpp - tests/unit/test_BLi_vs_cubic_interpolation.cpp - tests/unit/test_field_interpolation.cpp - tests/unit/test_interpolation_determination.cpp - tests/unit/test_interpolation_functions.cpp - tests/unit/test_numerical_derivative.cpp - tests/unit/test_openandorder.cpp - tests/unit/array_tests/test_Tensor3D.cpp - tests/unit/array_tests/test_XYZTensor3D.cpp - tests/unit/array_tests/test_XYZVectors.cpp - tests/unit/array_tests/test_Matrix.cpp - tests/unit/array_tests/test_Vector.cpp) + # find all .cpp files in the unit tests directory (and subdirectories). + file(GLOB_RECURSE UNIT_TEST_SRC "${CMAKE_SOURCE_DIR}/tests/unit/test_*.cpp") + add_executable(tdms_tests "${UNIT_TEST_SRC}") target_link_libraries(tdms_tests PRIVATE Catch2::Catch2WithMain diff --git a/tdms/tests/unit/array_tests/test_DTilde.cpp b/tdms/tests/unit/array_tests/test_DTilde.cpp new file mode 100644 index 000000000..38cf46333 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DTilde.cpp @@ -0,0 +1,92 @@ +/** + * @file test_DTilde.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Unit tests for the DTilde class + */ +#include +#include + +#include "arrays.h" + +using namespace std; + +TEST_CASE("DTilde: allocation and deallocation") { + + DTilde dt; + bool no_information_stored; + int dims[2]; + mxArray *test_input; + + // check no information is contained in the DTilde object + SECTION("No information stored on declaration") { + no_information_stored = + (dt.num_det_modes() == 0) && (!dt.x.has_elements()) && (!dt.y.has_elements()); + REQUIRE(no_information_stored); + } + + // initialise() method should exit without assignment if we pass in a pointer to an empty array + // create empty array + SECTION("Empty array input") { + dims[0] = 0; + dims[1] = 1; + test_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + // attempt assignment (2nd and 3rd args don't matter) + dt.initialise(test_input, 0, 0); + // should still be unassigned vectors + no_information_stored = + (dt.num_det_modes() == 0) && (!dt.x.has_elements()) && (!dt.y.has_elements()); + REQUIRE(no_information_stored); + } + + // assignment will throw error if we attempt to provide a non-empty, non-struct array + SECTION("Non-struct input") { + dims[0] = 2; + dims[1] = 3; + test_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(dt.initialise(test_input, 0, 0), runtime_error); + } + + // assignment will throw error if we attempt to provide a struct array that doesn't have two fields + SECTION("Struct with too many fields") { + dims[0] = 1; + dims[1] = 1; + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + test_input = mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(dt.initialise(test_input, 0, 0), runtime_error); + } + SECTION("Struct with too few fields") { + dims[0] = 1; + dims[1] = 1; + const char *too_few_names[1] = {"field1"}; + test_input = mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + CHECK_THROWS_AS(dt.initialise(test_input, 0, 0), runtime_error); + } + + // otherwise, we need to provide a struct with two fields, Dx_tilde and Dy_tilde + // these fields must contain (n_det_modes, n_rows, n_cols) arrays of doubles/complex + SECTION("Correct input") { + const char *fieldnames[] = {"Dx_tilde", "Dy_tilde"}; + dims[0] = 1; + dims[1] = 1; + const int target_n_det_modes = 5, n_rows = 6, n_cols = 4; + const int field_array_dims[3] = {target_n_det_modes, n_rows, n_cols}; + // create the struct + test_input = mxCreateStructArray(2, (const mwSize *) dims, 2, 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(3, (const mwSize *) field_array_dims, mxDOUBLE_CLASS, mxCOMPLEX); + mxSetField(test_input, 0, fieldnames[i], field_array_ptrs[i]); + } + // attempt to create a vector from this struct + REQUIRE_NOTHROW(dt.initialise(test_input, n_rows, n_cols)); + // check that we actually assigned values to the Vectors under the hood + bool information_stored = (dt.num_det_modes() == target_n_det_modes) && (dt.x.has_elements()) && + (dt.y.has_elements()); + CHECK(information_stored); + } + + // cleanup MATLAB memory + mxDestroyArray(test_input); +} diff --git a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp new file mode 100644 index 000000000..6403308af --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -0,0 +1,44 @@ +/** + * @file test_DetectorSensitivityArrays.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Unit tests for the DetectorSensitivityArrays class and its subclasses + */ +#include +#include +#include + +#include + +#include "arrays.h" +#include "globals.h" + +using namespace std; +using namespace tdms_math_constants; + +const double tol = 1e-16; + +TEST_CASE("DetectorSensitivityArrays") { + + // default constructor should set everything to nullptrs + // destructor uses fftw destroy, which handles nullptrs itself + DetectorSensitivityArrays empty_dsa; + bool all_are_nullptrs = (empty_dsa.cm==nullptr) && (empty_dsa.plan==nullptr) && (empty_dsa.v==nullptr); + REQUIRE(all_are_nullptrs); + + // now let's construct a non-trival object + DetectorSensitivityArrays dsa; + const int n_rows = 8, n_cols = 4; + dsa.initialise(n_rows, n_cols); + // we should be able to assign complex doubles to the elements of cm now + // to test the plan executation, we'd need an analytic DFT to hand + for (int i = 0; i < n_rows; i++) { + for (int j = 0; j < n_cols; j++) { + dsa.cm[i][j] = (double) i + IMAGINARY_UNIT * (double) j; + dsa.v[j * n_rows + i][0] = dsa.cm[i][j].real(); + dsa.v[j * n_rows + i][1] = dsa.cm[i][j].imag(); + } + } + // we can call the fftw_plan execution, which should place the 2D FFT into dsa.v + // simply checking executation is sufficient, as fftw should cover whether the FFT is actually meaningful in what it puts out + REQUIRE_NOTHROW(fftw_execute(dsa.plan)); +} diff --git a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp new file mode 100644 index 000000000..fe77e1748 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp @@ -0,0 +1,76 @@ +/** + * @file test_DispersiveMultiLayer.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Tests for the DispersiveMultiLayer class and its subclasses + */ +#include +#include +#include + +#include "arrays.h" + +using namespace std; +using Catch::Approx; + +const double tol = 1e-16; + +TEST_CASE("DispersiveMultiLayer: allocation and deallocation") { + + mxArray *matlab_input; + // Constructor should throw runtime_error at not recieving struct + // create a MATLAB pointer to something that isn't an array + SECTION("Non-struct input") { + int n_elements[2] = {2, 3}; + matlab_input = mxCreateNumericArray(2, (const mwSize *) n_elements, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(DispersiveMultiLayer(matlab_input), runtime_error); + } + + // Constructor should error if recieving an empty struct + // create a MATLAB pointer to an empty struct + SECTION("Empty struct input") { + const char* empty_fields[] = {}; + const int empty_dims[2] = {1,1}; + matlab_input = mxCreateStructArray(2, (const mwSize *) empty_dims, 0, empty_fields); + CHECK_THROWS_AS(DispersiveMultiLayer(matlab_input), runtime_error); + } + + // For successful construction, we need to build a MATLAB struct with 9 fields + // these are the fieldnames that are expected + SECTION("Successful construction") { + const int n_fields = 9; + const char *fieldnames[n_fields] = {"alpha", "beta", "gamma", "kappa_x", "kappa_y", + "kappa_z", "sigma_x", "sigma_y", "sigma_z"}; + const int n_field_elements = 5; + // build our struct + const int dims[2] = {1, 1}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 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_field_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 < 5; 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 < 5; i++) { + CHECK(dml.alpha[i] == Approx(i).epsilon(tol)); + CHECK(dml.beta[i] == Approx(i).epsilon(tol)); + CHECK(dml.gamma[i] == Approx(i).epsilon(tol)); + CHECK(dml.kappa.x[i] == Approx(i).epsilon(tol)); + CHECK(dml.kappa.y[i] == Approx(i).epsilon(tol)); + CHECK(dml.kappa.z[i] == Approx(i).epsilon(tol)); + CHECK(dml.sigma.x[i] == Approx(i).epsilon(tol)); + CHECK(dml.sigma.y[i] == Approx(i).epsilon(tol)); + CHECK(dml.sigma.z[i] == Approx(i).epsilon(tol)); + } + } + + // cleanup MATLAB array + mxDestroyArray(matlab_input); +} diff --git a/tdms/tests/unit/array_tests/test_FieldSample.cpp b/tdms/tests/unit/array_tests/test_FieldSample.cpp new file mode 100644 index 000000000..15e4d9f64 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_FieldSample.cpp @@ -0,0 +1,129 @@ +/** + * @file test_FieldSample.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Unit tests for the FieldSample class and its subclasses + */ +#include +#include + +#include "arrays.h" + +using namespace std; + +const double tol = 1e-16; + +TEST_CASE("FieldSample") { + + mxArray *matlab_input; + int dims[2] = {1, 1}; + const char *fieldnames[] = {"i", "j", "k", "n"}; + + // attempting to construct with an empty array should exit with default values + SECTION("Empty array input") { + dims[0] = 0; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + // attempt assignment (2nd and 3rd args don't matter) + FieldSample empty_fs(matlab_input); + // should still be unassigned vectors + bool no_vectors_set = (!empty_fs.i.has_elements()) && (!empty_fs.j.has_elements()) && + (!empty_fs.k.has_elements()) && (!empty_fs.n.has_elements()); + REQUIRE(no_vectors_set); + } + + // construction fails if provided a non-empty, non-struct array + SECTION("Non-struct input") { + dims[0] = 2; + dims[1] = 3; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(FieldSample(matlab_input), runtime_error); + } + + // construction fails if provided a struct array that doesn't have 4 fields + SECTION("Struct with too many fields") { + const char *too_mny_names[5] = {"field1", "field2", "field3", "field4", "field5"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 5, too_mny_names); + CHECK_THROWS_AS(FieldSample(matlab_input), runtime_error); + } + SECTION("Struct with too few fields") { + const char *too_few_names[1] = {"field1"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + CHECK_THROWS_AS(FieldSample(matlab_input), runtime_error); + } + + // expecting a struct with fields i, j, k, and n + SECTION("Expected input (populated)") { + const int number_of_vector_elements = 8; + const int vector_dims[2] = {1, number_of_vector_elements}; + // create the struct + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 4, fieldnames); + // create vectors to place in the fields of the struct + mxArray *i_vector = + mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); + mxArray *j_vector = + mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); + mxArray *k_vector = + mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); + mxArray *n_vector = + mxCreateNumericArray(2, (const mwSize *) vector_dims, mxDOUBLE_CLASS, mxREAL); + // populate the vectors with some information + mxDouble *place_i_data = mxGetPr(i_vector);//< 0,1,2,... + mxDouble *place_j_data = mxGetPr(j_vector);//< number_of_vector_elements-1, -2, -3... + mxDouble *place_k_data = mxGetPr(k_vector);//< 0,1,2,...,N/2,0,1,2,... + mxDouble *place_n_data = mxGetPr(n_vector);//< 1, 1/2, 1/3, ... + for (int i = 0; i < 4; i++) { + place_i_data[i] = i; + place_j_data[i] = number_of_vector_elements - (i + 1); + place_k_data[i] = i % (number_of_vector_elements / 2); + place_n_data[i] = 1. / (double) (i + 1); + } + // set the vectors to be the field values + mxSetField(matlab_input, 0, fieldnames[0], i_vector); + mxSetField(matlab_input, 0, fieldnames[1], j_vector); + mxSetField(matlab_input, 0, fieldnames[2], k_vector); + mxSetField(matlab_input, 0, fieldnames[3], n_vector); + // we should be able to construct things now + REQUIRE_NOTHROW(FieldSample(matlab_input)); + FieldSample fs(matlab_input); + // check that the tensor attribute is the correct size + const mwSize *dims = mxGetDimensions(fs.mx); + for (int i = 0; i < 4; i++) { CHECK(dims[i] == number_of_vector_elements); } + // check that we did indeed copy the data across + for (int i = 0; i < 4; i++) { + CHECK(abs(fs.i[i] - i) < tol); + CHECK(abs(fs.j[i] - (number_of_vector_elements - (i + 1))) < tol); + CHECK(abs(fs.k[i] - (i % (number_of_vector_elements / 2))) < tol); + CHECK(abs(fs.n[i] - 1. / (double) (i + 1)) < tol); + } + } + + // alternatively, we can pass in empty vectors to the constructor and it should create an empty 4D tensor + SECTION("Expected inputs (empty vectors)") { + const int empty_vector_dims[2] = {1, 0}; + // create the struct + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 4, fieldnames); + // create vectors to place in the fields of the struct + mxArray *empty_i_vector = + mxCreateNumericArray(2, (const mwSize *) empty_vector_dims, mxINT32_CLASS, mxREAL); + mxArray *empty_j_vector = + mxCreateNumericArray(2, (const mwSize *) empty_vector_dims, mxINT32_CLASS, mxREAL); + mxArray *empty_k_vector = + mxCreateNumericArray(2, (const mwSize *) empty_vector_dims, mxINT32_CLASS, mxREAL); + mxArray *empty_n_vector = + mxCreateNumericArray(2, (const mwSize *) empty_vector_dims, mxDOUBLE_CLASS, mxREAL); + mxSetField(matlab_input, 0, fieldnames[0], empty_i_vector); + mxSetField(matlab_input, 0, fieldnames[1], empty_j_vector); + mxSetField(matlab_input, 0, fieldnames[2], empty_k_vector); + mxSetField(matlab_input, 0, fieldnames[3], empty_n_vector); + // we should be able to construct things now + REQUIRE_NOTHROW(FieldSample(matlab_input)); + FieldSample fs_from_empty_vectors(matlab_input); + // check that all vectors are empty flags as being true + CHECK(!fs_from_empty_vectors.all_vectors_are_non_empty()); + // check that the tensor attribute is the correct size + const mwSize *empty_tensor_dims = mxGetDimensions(fs_from_empty_vectors.mx); + for (int i = 0; i < 4; i++) { CHECK(empty_tensor_dims[i] == 0); } + } + + // cleanup + mxDestroyArray(matlab_input); +} diff --git a/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp new file mode 100644 index 000000000..fe6dd7e0f --- /dev/null +++ b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp @@ -0,0 +1,93 @@ +/** + * @file test_FrequencyVectors.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Tests for the FrequencyVectors class and its subclasses + */ +#include +#include + +#include "arrays.h" + +using namespace std; + +const double tol = 1e-16; + +TEST_CASE("FrequencyVectors: allocation and deallocation") { + + FrequencyVectors fv; + mxArray *matlab_input; + int dims[2] = {1, 1}; + // members should start unassigned + bool not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); + REQUIRE(not_assigned); + + // initialise() method should exit without assignment if we pass in a pointer to an empty array + // create empty array + SECTION("Empty array input") { + dims[0] = 0; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + // attempt assignment + fv.initialise(matlab_input); + // should still be unassigned vectors + not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); + REQUIRE(not_assigned); + } + + // assignment will throw error if we attempt to provide a non-empty, non-struct array + SECTION("Non-struct input") { + dims[0] = 2; dims[1] = 3; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(fv.initialise(matlab_input), runtime_error); + } + + // assignment will throw error if we attempt to provide a struct array that doesn't have two fields + SECTION("Struct with too many inputs") { + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(fv.initialise(matlab_input), runtime_error); + } + SECTION("Struct with too few inputs") { + const char *too_few_names[1] = {"field1"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + CHECK_THROWS_AS(fv.initialise(matlab_input), runtime_error); + } + + // otherwise, we need to provide a struct, whose fields are vectors that can be assigned to + // setup for our struct + SECTION("Expected input") { + const char *fieldnames[] = {"fx_vec", "fy_vec"}; + const int n_field_array_elements = 10; + const int field_array_dims[2] = {1, n_field_array_elements}; + // create the struct + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 2, 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_dims, 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_field_array_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 + not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); + bool expected_size = + (fv.x.size() == n_field_array_elements && fv.y.size() == n_field_array_elements); + bool assigned_and_correct_size = ((!not_assigned) && expected_size); + REQUIRE(assigned_and_correct_size); + // and the values themselves are what we expect + for (int i = 0; i < n_field_array_elements; i++) { + CHECK(abs(fv.x[i] - 1. / ((double) (i + 1))) < tol); + CHECK(abs(fv.y[i] + 1. / ((double) (i + 1))) < tol); + } + } + + // cleanup + mxDestroyArray(matlab_input); +} diff --git a/tdms/tests/unit/array_tests/test_IncidentField.cpp b/tdms/tests/unit/array_tests/test_IncidentField.cpp new file mode 100644 index 000000000..ad5ade613 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_IncidentField.cpp @@ -0,0 +1,90 @@ +/** + * @file test_IncidentField.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Unit tests for the IncidentField class + */ +#include +#include + +#include "arrays.h" + +using namespace std; + +const double tol = 1e-16; + +TEST_CASE("IncidentField: allocation and deallocation") { + + mxArray *matlab_input; + int dims[2] = {1, 1}; + + // construction should fail if given an empty array + SECTION("Empty array input") { + dims[0] = 0; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + // attempt construction (2nd and 3rd args don't matter) + REQUIRE_THROWS_AS(IncidentField(matlab_input), runtime_error); + } + + // construction fails if provided a non-empty, non-struct array + SECTION("Non-struct input") { + dims[0] = 2; dims[1] = 3; + matlab_input = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(IncidentField(matlab_input), runtime_error); + } + + // construction fails if provided a struct array that doesn't have two fields + SECTION("Struct with too many fields") { + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(IncidentField(matlab_input), runtime_error); + } + SECTION("Struct with too few fields") { + const char *too_few_names[1] = {"field1"}; + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + CHECK_THROWS_AS(IncidentField(matlab_input), runtime_error); + } + + // otherwise, we need to provide a struct with two fields, Dx_tilde and Dy_tilde + // these fields must contain (n_det_modes, n_rows, n_cols) arrays of doubles/complex + SECTION("Expected input") { + const char *fieldnames[] = {"exi", "eyi"}; + const int n_rows = 6, n_cols = 4, n_layers = 5; + const int field_array_dims[3] = {n_rows, n_cols, n_layers}; + // create the struct + matlab_input = mxCreateStructArray(2, (const mwSize *) dims, 2, 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(3, (const mwSize *) field_array_dims, mxDOUBLE_CLASS, mxREAL); + mxDouble *place_data = mxGetPr(field_array_ptrs[i]); + for (int ii = 0; ii < n_rows; ii++) { + for (int jj = 0; jj < n_cols; jj++) { + for (int kk = 0; kk < n_layers; kk++) { + *(place_data + kk * n_cols * n_rows + jj * n_rows + ii) = + 1. / ((double) (kk + jj + ii + 1)); + } + } + } + mxSetField(matlab_input, 0, fieldnames[i], field_array_ptrs[i]); + } + // attempt to create a vector from this struct + IncidentField i_field(matlab_input); + // check that we actually assigned values to the Vectors under the hood + bool information_stored = (i_field.x.has_elements()) && (i_field.y.has_elements()); + REQUIRE(information_stored); + for (int ii = 0; ii < n_rows; ii++) { + for (int jj = 0; jj < n_cols; jj++) { + for (int kk = 0; kk < n_layers; kk++) { + bool element_set_correctly = + (abs(i_field.x[kk][jj][ii] - 1. / ((double) (kk + jj + ii + 1))) < tol) && + (abs(i_field.y[kk][jj][ii] - 1. / ((double) (kk + jj + ii + 1))) < tol); + CHECK(element_set_correctly); + } + } + } + } + + // cleanup + mxDestroyArray(matlab_input); +} diff --git a/tdms/tests/unit/array_tests/test_Matrix.cpp b/tdms/tests/unit/array_tests/test_Matrix.cpp index 7f73c1067..fc8feb00a 100644 --- a/tdms/tests/unit/array_tests/test_Matrix.cpp +++ b/tdms/tests/unit/array_tests/test_Matrix.cpp @@ -9,29 +9,38 @@ #include "arrays.h" TEST_CASE("Matrix: allocation and deallocation") { - SPDLOG_INFO("== Testing Matrix allocation/deallocation"); // mock-up dimensions int n_rows = 4, n_cols = 8; // create a Matrix via the default constructor - Matrix M_int; - // although created, the matrix should not have any elements, and should be flagged as such - REQUIRE(!M_int.has_elements()); - // we need to use allocate to provide space for elements - M_int.allocate(n_rows, n_cols); - REQUIRE(M_int.has_elements()); + SECTION("Default constructor") { + Matrix M_int; + // although created, the matrix should not have any elements, and should be flagged as such + REQUIRE(!M_int.has_elements()); + // we need to use allocate to provide space for elements + M_int.allocate(n_rows, n_cols); + REQUIRE(M_int.has_elements()); + + // should be able to assign to these values without seg faults now + for (int i = 0; i < n_rows; i++) { + for (int j = 0; j < n_cols; j++) { + CHECK_NOTHROW(M_int[i][j] = 0); + } + } + } // create a Matrix using the overloaded constructor, and fill it with 1s - Matrix M_double(n_rows, n_cols); - // because we used the overloaded constructor, the matrix should have elements - REQUIRE(M_double.has_elements()); + SECTION("Overloaded constructor") { + Matrix M_double(n_rows, n_cols); + // because we used the overloaded constructor, the matrix should have elements + REQUIRE(M_double.has_elements()); - // should be able to assign to these values without seg faults now - for (int i = 0; i < n_rows; i++) { - for (int j = 0; j < n_cols; j++) { - CHECK_NOTHROW(M_int[i][j] = 0); - CHECK_NOTHROW(M_double[i][j] = 1.); + // should be able to assign to these values without seg faults now + for (int i = 0; i < n_rows; i++) { + for (int j = 0; j < n_cols; j++) { + CHECK_NOTHROW(M_double[i][j] = 1.); + } } } } diff --git a/tdms/tests/unit/array_tests/test_Tensor3D.cpp b/tdms/tests/unit/array_tests/test_Tensor3D.cpp index 6b762beea..4f36ee0f2 100644 --- a/tdms/tests/unit/array_tests/test_Tensor3D.cpp +++ b/tdms/tests/unit/array_tests/test_Tensor3D.cpp @@ -11,50 +11,73 @@ const double tol = 1e-16; -TEST_CASE("Tensor3D: allocation and deallocation") { - SPDLOG_INFO("== Testing Tensor3D allocation/deallocation"); - // some mock-up array dimensions - int n_layers = 4, n_cols = 8, n_rows = 16; +TEST_CASE("Tensor3D: construction") { + // some mock-up array dimensions + int n_layers = 4, n_cols = 8, n_rows = 16; + Tensor3D *t3d; + + // we should also check what happens when we use the overloaded constructor, providing a pre-built pointer to a "Tensor" in memory + double ***p = (double ***) malloc(n_layers * sizeof(double **)); + for (int k = 0; k < n_layers; k++) { + p[k] = (double **) malloc(n_cols * sizeof(double *)); + for (int j = 0; j < n_cols; j++) { p[k][j] = (double *) malloc(n_rows * sizeof(double)); } + } + + // construction via default constructor + SECTION("Default constructor") { // default constructor should assign all dimensions to 0, and the tensor itself should be a nullptr - Tensor3D *default_constructed_tensor = new Tensor3D; + t3d = new Tensor3D; // should have no elements - REQUIRE(!(default_constructed_tensor->has_elements())); + REQUIRE(!(t3d->has_elements())); // let us assign some free memory to this tensor via allocate() - default_constructed_tensor->allocate(n_layers, n_cols, n_rows); + t3d->allocate(n_layers, n_cols, n_rows); // we should now "have elements", even though they are unassigned - REQUIRE(default_constructed_tensor->has_elements()); - // now we can try to zero all the elements in the array, the Frobenius norm of the array should be zero if this was sucessful - default_constructed_tensor->zero(); - REQUIRE(abs(default_constructed_tensor->frobenius()) < tol); - - // we should also check what happens when we use the overloaded constructor, providing a pre-built pointer to a "Tensor" in memory - int ***p = (int ***) malloc(n_layers * sizeof(int **)); - for (int k = 0; k < n_layers; k++) { - p[k] = (int **) malloc(n_cols * sizeof(int *)); - for (int j = 0; j < n_cols; j++) { - p[k][j] = (int *) malloc(n_rows * sizeof(int)); + REQUIRE(t3d->has_elements()); + } + + // construction via overloaded constructor + SECTION("Overloaded constructor") { + t3d = new Tensor3D(p, n_layers, n_cols, n_rows); + // this tensor should be flagged as "having elements", since we provided a pointer in the constructor + REQUIRE(t3d->has_elements()); + } + + // cleanup memory + delete t3d; +} + +TEST_CASE("Tensor3D: zero, frobenius") { + + int n_layers = 4, n_cols = 8, n_rows = 16; + Tensor3D t3d; + + t3d.allocate(n_layers, n_cols, n_rows); + // zero the entire array and check this has happened + t3d.zero(); + // we should be able to flag this tensor has elements, so the bool should be set to true + bool allocated_and_zero = t3d.has_elements(); + for (int k = 0; k < n_layers; k++) { + for (int j = 0; j < n_cols; j++) { + for (int i = 0; i < n_rows; i++) { + allocated_and_zero = allocated_and_zero && (abs(t3d[k][j][i]) < tol); } } - // assign some values to this tensor. We'll go with =0 if i+j+k is even, and =1 if odd - // this gives us 4*8*16/2 = 4^4 = 256 non-zero entries, which are 1, so the analytic norm is 16. - for (int k=0; k *overloaded_constructed_tensor = new Tensor3D(p, n_layers, n_cols, n_rows); - // this tensor should be flagged as "having elements", since we provided a pointer in the constructor - REQUIRE(overloaded_constructed_tensor->has_elements()); - // it should also have the target Frobenius norm, since we preallocated our array via an int*** - REQUIRE(abs(overloaded_constructed_tensor->frobenius()-target_fro) v_double; REQUIRE(!v_double.has_elements()); REQUIRE(v_double.size() == 0); // create a MATLAB array to cast to - const int n_elements[1] = {8}; // MATLAB'S LOGIC IS BEYOND MY UNDERSTANDING, CAN'T have n_elements as an int, must be int* - mxArray *array = mxCreateNumericArray(1, (const mwSize *) n_elements, mxUINT8_CLASS, mxREAL); - REQUIRE( array != NULL ); + const int n_elements = 8; + const int dims[2] = {1, n_elements}; + mxArray *array = mxCreateNumericArray(2, (const mwSize *) dims, mxDOUBLE_CLASS, mxREAL); + mxDouble *where_to_place_data = mxGetPr(array); + // data is just the integers starting from 0 + for (int i = 0; i < n_elements; i++) { where_to_place_data[i] = (double) i; } // assign this MATLAB array to a vector - Vector v_int(array); - REQUIRE(v_int.has_elements()); - REQUIRE(v_int.size()==n_elements[0]); + Vector v_from_matlab(array); + bool has_elements_and_right_size = + (v_from_matlab.has_elements() && v_from_matlab.size() == n_elements); + REQUIRE(has_elements_and_right_size); + // check that the data is the _right_ data too + for (int i = 0; i < n_elements; i++) { CHECK(v_from_matlab[i] == i); } // cleanup our MATLAB array mxDestroyArray(array); diff --git a/tdms/tests/unit/array_tests/test_XYZTensor3D.cpp b/tdms/tests/unit/array_tests/test_XYZTensor3D.cpp index d99cbdc5f..7599dc4da 100644 --- a/tdms/tests/unit/array_tests/test_XYZTensor3D.cpp +++ b/tdms/tests/unit/array_tests/test_XYZTensor3D.cpp @@ -12,8 +12,8 @@ using namespace std; -TEST_CASE("Test XYZTensor allocation") { - SPDLOG_INFO("== Testing XYZTensor3D allocation/deallocation"); +TEST_CASE("XYZTensor") { + // dimensions for the test-tensor int n_layers = 4, n_cols = 8, n_rows = 16; diff --git a/tdms/tests/unit/array_tests/test_XYZVectors.cpp b/tdms/tests/unit/array_tests/test_XYZVectors.cpp index 825b4640d..df96e28ce 100644 --- a/tdms/tests/unit/array_tests/test_XYZVectors.cpp +++ b/tdms/tests/unit/array_tests/test_XYZVectors.cpp @@ -14,8 +14,8 @@ using std::accumulate; const double tol = 1e-8; -TEST_CASE("Test XYZVectors allocation") { - SPDLOG_INFO("== Testing XYZVectors allocation/deallocation"); +TEST_CASE("XYZVectors") { + // dimensions for the test-tensor const int nx = 4, ny = 8, nz = 16; @@ -35,6 +35,7 @@ TEST_CASE("Test XYZVectors allocation") { REQUIRE(v.y != nullptr); v.set_ptr(AxialDirection::Z, z_vec); REQUIRE(v.z != nullptr); + // and manipulating the components double x_tot = accumulate(v.x, v.x + nx, 0.); CHECK(abs(x_tot - 0.) <= tol);