From 63b63399121f296d65796638dcdf0777e983e701 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 2 Nov 2022 14:33:06 +0000 Subject: [PATCH 01/14] DispersiveMultLayer tests --- tdms/CMakeLists.txt | 3 +- .../array_tests/test_DispersiveMultiLayer.cpp | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp diff --git a/tdms/CMakeLists.txt b/tdms/CMakeLists.txt index c1a01b4d2..e82a87810 100644 --- a/tdms/CMakeLists.txt +++ b/tdms/CMakeLists.txt @@ -148,7 +148,8 @@ if (BUILD_TESTING) 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) + tests/unit/array_tests/test_Vector.cpp + tests/unit/array_tests/test_DispersiveMultiLayer.cpp) target_link_libraries(tdms_tests PRIVATE Catch2::Catch2WithMain 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..66704c87d --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp @@ -0,0 +1,68 @@ +/** + * @file test_DispersiveMultiLayer.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Tests for the DispersiveMultiLayer class and its subclasses + */ +#include +#include + +#include "arrays.h" + +using namespace std; + +TEST_CASE("DispersiveMultiLayer: allocation and deallocation") { + SPDLOG_INFO("== Testing DispersiveMultiLayer class"); + + // Constructor should throw runtime_error at not recieving struct + // create a MATLAB pointer to something that isn't an array + int n_elements[2] = {2, 3}; + mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) n_elements, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(DispersiveMultiLayer(not_a_struct), runtime_error); + // cleanup + mxDestroyArray(not_a_struct); + + // Constructor should error if recieving an empty struct + // create a MATLAB pointer to an empty struct + const char* empty_fields[] = {}; + const int empty_dims[2] = {1,1}; + mxArray *empty_struct_pointer = mxCreateStructArray(2, (const mwSize*) empty_dims, 0, empty_fields); + CHECK_THROWS_AS(DispersiveMultiLayer(empty_struct_pointer), runtime_error); + mxDestroyArray(empty_struct_pointer); + + // For successful construction, we need to build a MATLAB struct with 9 fields + // these are the fieldnames that are expected + 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}; + mxArray *useful_struct_pointer = 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(useful_struct_pointer, 0, fieldnames[i], field_array_ptrs[i]); + } + // we should now be able to create a DispersiveMultiLayer object + REQUIRE_NOTHROW(DispersiveMultiLayer(useful_struct_pointer)); + DispersiveMultiLayer dml(useful_struct_pointer); + // now check that the data has been correctly assigned + for (int i = 0; i < 5; i++) { + CHECK(dml.alpha[i] == (double) i); + CHECK(dml.beta[i] == (double) i); + CHECK(dml.gamma[i] == (double) i); + CHECK(dml.kappa.x[i] == (double) i); + CHECK(dml.kappa.y[i] == (double) i); + CHECK(dml.kappa.z[i] == (double) i); + CHECK(dml.sigma.x[i] == (double) i); + CHECK(dml.sigma.y[i] == (double) i); + CHECK(dml.sigma.z[i] == (double) i); + } + // cleanup + mxDestroyArray(useful_struct_pointer); +} From cd420f37d77540242515a286f97e33a23303b004 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 2 Nov 2022 15:06:29 +0000 Subject: [PATCH 02/14] CMakeLists will now auto-find all array_tests files --- tdms/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tdms/CMakeLists.txt b/tdms/CMakeLists.txt index e82a87810..3a0d4f020 100644 --- a/tdms/CMakeLists.txt +++ b/tdms/CMakeLists.txt @@ -136,6 +136,11 @@ install(TARGETS tdms) if (BUILD_TESTING) enable_testing() + # find all the array.h dataype tests + file(GLOB ARRAY_TEST_FILES + "${CMAKE_SOURCE_DIR}/tests/unit/array_tests/test_*.cpp" + ) + add_executable(tdms_tests tests/unit/test_fields.cpp tests/unit/test_BLi_vs_cubic_interpolation.cpp @@ -144,12 +149,7 @@ if (BUILD_TESTING) 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 - tests/unit/array_tests/test_DispersiveMultiLayer.cpp) + ${ARRAY_TEST_FILES}) target_link_libraries(tdms_tests PRIVATE Catch2::Catch2WithMain From 83fec4618dfc8563b3ef30c4f2a523841bbe8679 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Wed, 2 Nov 2022 16:25:12 +0000 Subject: [PATCH 03/14] FrequencyVectors test, Vector class cleanup --- .../array_tests/test_FrequencyVectors.cpp | 91 +++++++++++++++++++ tdms/tests/unit/array_tests/test_Vector.cpp | 18 ++-- 2 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 tdms/tests/unit/array_tests/test_FrequencyVectors.cpp 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..2aa68b6d1 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp @@ -0,0 +1,91 @@ +/** + * @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") { + SPDLOG_INFO("== Testing FrequencyVectors class"); + // there is no custom constructor for this class + FrequencyVectors fv; + // but the members should start as nullptrs + 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 + const int empty_dims[2] = {0, 1}; + mxArray *empty_array = + mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); + // attempt assignment + fv.initialise(empty_array); + // should still be unassigned vectors + not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); + REQUIRE(not_assigned); + // cleanup the empty array mess we just made + mxDestroyArray(empty_array); + + // assignment will throw error if we attempt to provide a non-empty, non-struct array + int dims[2] = {2, 3}; + mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(fv.initialise(not_a_struct), runtime_error); + // cleanup + mxDestroyArray(not_a_struct); + + // assignment will throw error if we attempt to provide a struct array that doesn't have two fields + const char *too_few_names[1] = {"field1"}; + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + mxArray *struct_with_too_few_fields = + mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + mxArray *struct_with_too_mny_fields = + mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(fv.initialise(struct_with_too_few_fields), runtime_error); + CHECK_THROWS_AS(fv.initialise(struct_with_too_mny_fields), runtime_error); + // cleanup + mxDestroyArray(struct_with_too_few_fields); + mxDestroyArray(struct_with_too_mny_fields); + + // otherwise, we need to provide a struct, whose fields are vectors that can be assigned to + // setup for our struct + const char *fieldnames[] = {"fx_vec", "fy_vec"}; + dims[0] = 1; + dims[1] = 1; + const int n_field_array_elements = 10; + const int field_array_dims[2] = {1, n_field_array_elements}; + // create the struct + mxArray *example_struct = 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(example_struct, 0, fieldnames[i], field_array_ptrs[i]); + } + // attempt to create a vector from this struct + REQUIRE_NOTHROW(fv.initialise(example_struct)); + // 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(example_struct); +} diff --git a/tdms/tests/unit/array_tests/test_Vector.cpp b/tdms/tests/unit/array_tests/test_Vector.cpp index 9e1ed609a..27bab49c0 100644 --- a/tdms/tests/unit/array_tests/test_Vector.cpp +++ b/tdms/tests/unit/array_tests/test_Vector.cpp @@ -17,14 +17,20 @@ TEST_CASE("Vector: allocation and deallocation") { 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); From 63459d561f1b98c043b18eb95252be199a15d3d6 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Wed, 2 Nov 2022 16:56:18 +0000 Subject: [PATCH 04/14] Recursive globbing unit test source files. (#160) * Recursive globbing unit test source files. * Specify test_*.cpp Co-authored-by: Will Graham <32364977+willGraham01@users.noreply.github.com> Co-authored-by: Will Graham <32364977+willGraham01@users.noreply.github.com> --- tdms/CMakeLists.txt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/tdms/CMakeLists.txt b/tdms/CMakeLists.txt index 3a0d4f020..0bf6dad4a 100644 --- a/tdms/CMakeLists.txt +++ b/tdms/CMakeLists.txt @@ -136,20 +136,9 @@ install(TARGETS tdms) if (BUILD_TESTING) enable_testing() - # find all the array.h dataype tests - file(GLOB ARRAY_TEST_FILES - "${CMAKE_SOURCE_DIR}/tests/unit/array_tests/test_*.cpp" - ) - - 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 - ${ARRAY_TEST_FILES}) + # 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 From 1cc812319005c5a52d7dd8c1de79680f4ed854ff Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 14:12:33 +0000 Subject: [PATCH 05/14] DTilde test --- tdms/tests/unit/array_tests/test_DTilde.cpp | 82 +++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tdms/tests/unit/array_tests/test_DTilde.cpp 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..d254392b5 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DTilde.cpp @@ -0,0 +1,82 @@ +/** + * @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; + +const double tol = 1e-16; + +TEST_CASE("DTilde: allocation and deallocation") { + SPDLOG_INFO("== Testing DTilde class"); + // no custom constructor + DTilde dt; + // check no information is contained in the DTilde object + bool 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 + const int empty_dims[2] = {0, 1}; + mxArray *empty_array = + mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); + // attempt assignment (2nd and 3rd args don't matter) + dt.initialise(empty_array, 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); + // cleanup the empty array mess we just made + mxDestroyArray(empty_array); + + // assignment will throw error if we attempt to provide a non-empty, non-struct array + int dims[2] = {2, 3}; + mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(dt.initialise(not_a_struct, 0, 0), runtime_error); + // cleanup + mxDestroyArray(not_a_struct); + + // assignment will throw error if we attempt to provide a struct array that doesn't have two fields + const char *too_few_names[1] = {"field1"}; + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + mxArray *struct_with_too_few_fields = + mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + mxArray *struct_with_too_mny_fields = + mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(dt.initialise(struct_with_too_few_fields, 0, 0), runtime_error); + CHECK_THROWS_AS(dt.initialise(struct_with_too_mny_fields, 0, 0), runtime_error); + // cleanup + mxDestroyArray(struct_with_too_few_fields); + mxDestroyArray(struct_with_too_mny_fields); + + // 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 + 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 + mxArray *example_struct = 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(example_struct, 0, fieldnames[i], field_array_ptrs[i]); + } + // attempt to create a vector from this struct + REQUIRE_NOTHROW(dt.initialise(example_struct, 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 + mxDestroyArray(example_struct); +} From fa82b83729c8146cd4cff45437bd90d2e8ce421f Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 14:34:26 +0000 Subject: [PATCH 06/14] IncidentField tests --- tdms/tests/unit/array_tests/test_DTilde.cpp | 2 - .../unit/array_tests/test_IncidentField.cpp | 88 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tdms/tests/unit/array_tests/test_IncidentField.cpp diff --git a/tdms/tests/unit/array_tests/test_DTilde.cpp b/tdms/tests/unit/array_tests/test_DTilde.cpp index d254392b5..d09fde5fa 100644 --- a/tdms/tests/unit/array_tests/test_DTilde.cpp +++ b/tdms/tests/unit/array_tests/test_DTilde.cpp @@ -10,8 +10,6 @@ using namespace std; -const double tol = 1e-16; - TEST_CASE("DTilde: allocation and deallocation") { SPDLOG_INFO("== Testing DTilde class"); // no custom constructor 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..54401d005 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_IncidentField.cpp @@ -0,0 +1,88 @@ +/** + * @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") { + SPDLOG_INFO("== Testing IncidentField class"); + // construction should fail if given an empty array + const int empty_dims[2] = {0, 1}; + mxArray *empty_array = + mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); + // attempt construction (2nd and 3rd args don't matter) + REQUIRE_THROWS_AS(IncidentField(empty_array), runtime_error); + // cleanup the empty array mess we just made + mxDestroyArray(empty_array); + + // construction fails if provided a non-empty, non-struct array + int dims[2] = {2, 3}; + mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); + CHECK_THROWS_AS(IncidentField(not_a_struct), runtime_error); + // cleanup + mxDestroyArray(not_a_struct); + + // construction fails if provided a struct array that doesn't have two fields + const char *too_few_names[1] = {"field1"}; + const char *too_mny_names[3] = {"field1", "field2", "field3"}; + mxArray *struct_with_too_few_fields = + mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); + mxArray *struct_with_too_mny_fields = + mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); + CHECK_THROWS_AS(IncidentField(struct_with_too_few_fields), runtime_error); + CHECK_THROWS_AS(IncidentField(struct_with_too_mny_fields), runtime_error); + // cleanup + mxDestroyArray(struct_with_too_few_fields); + mxDestroyArray(struct_with_too_mny_fields); + + // 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 + const char *fieldnames[] = {"exi", "eyi"}; + dims[0] = 1; + dims[1] = 1; + 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 + mxArray *example_struct = 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(example_struct, 0, fieldnames[i], field_array_ptrs[i]); + } + // attempt to create a vector from this struct + REQUIRE_NOTHROW(IncidentField(example_struct)); + IncidentField i_field(example_struct); + // 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(example_struct); +} From 1ce6c396a78cd21d96c93c81965448277c6839e1 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 15:46:38 +0000 Subject: [PATCH 07/14] FieldSample tests --- .../unit/array_tests/test_FieldSample.cpp | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tdms/tests/unit/array_tests/test_FieldSample.cpp 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..f01ec872e --- /dev/null +++ b/tdms/tests/unit/array_tests/test_FieldSample.cpp @@ -0,0 +1,95 @@ +/** + * @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; + +TEST_CASE("FieldSample: allocation and deallocation") { + SPDLOG_INFO("== Testing FieldSample class"); + + // attempting to construct with an empty array should exit with default values + const int empty_dims[2] = {0, 1}; + mxArray *empty_array = + mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); + // attempt assignment (2nd and 3rd args don't matter) + FieldSample empty_fs(empty_array); + // 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); + // cleanup the empty array mess we just made + mxDestroyArray(empty_array); + + // should really test whether the inputs being the wrong size/shape/etc are bad, but equally this is pointless, as we're just checking against the same things + // as such, let's just skip to the correct constructor + + // expecting a struct with fields i, j, k, and n + const char *fieldnames[] = {"i", "j", "k", "n"}; + const int struct_dims[2] = {1, 1}; + const int number_of_vector_elements = 8; + const int vector_dims[2] = {1, number_of_vector_elements}; + // create the struct + mxArray *populated_struct = mxCreateStructArray(2, (const mwSize *) struct_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); + mxSetField(populated_struct, 0, fieldnames[0], i_vector); + mxArray *j_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); + mxSetField(populated_struct, 0, fieldnames[1], j_vector); + mxArray *k_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); + mxSetField(populated_struct, 0, fieldnames[2], k_vector); + mxArray *n_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxDOUBLE_CLASS, mxREAL); + mxSetField(populated_struct, 0, fieldnames[3], n_vector); + // 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); + } + // we should be able to construct things now + REQUIRE_NOTHROW(FieldSample(populated_struct)); + FieldSample fs(populated_struct); + // 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); + } + // cleanup + mxDestroyArray(populated_struct); + // FieldSample destructor will deallocate fs.tensor correctly + + // alternatively, we can pass in empty vectors to the constructor and it should create an empty 4D tensor + const int empty_vector_dims[2] = {1, 0}; + // create the struct + mxArray *struct_with_empty_vectors = mxCreateStructArray(2, (const mwSize *) struct_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(struct_with_empty_vectors, 0, fieldnames[0], empty_i_vector); + mxSetField(struct_with_empty_vectors, 0, fieldnames[1], empty_j_vector); + mxSetField(struct_with_empty_vectors, 0, fieldnames[2], empty_k_vector); + mxSetField(struct_with_empty_vectors, 0, fieldnames[3], empty_n_vector); + // we should be able to construct things now + REQUIRE_NOTHROW(FieldSample(struct_with_empty_vectors)); + FieldSample fs_from_empty_vectors(struct_with_empty_vectors); + // 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(struct_with_empty_vectors); +} From 921c7a1ceb36b13f60c01430f9bcf24835e5e7c0 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 15:53:38 +0000 Subject: [PATCH 08/14] Missed data validation check --- .../unit/array_tests/test_FieldSample.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tdms/tests/unit/array_tests/test_FieldSample.cpp b/tdms/tests/unit/array_tests/test_FieldSample.cpp index f01ec872e..85c57b1d7 100644 --- a/tdms/tests/unit/array_tests/test_FieldSample.cpp +++ b/tdms/tests/unit/array_tests/test_FieldSample.cpp @@ -10,6 +10,8 @@ using namespace std; +const double tol = 1e-16; + TEST_CASE("FieldSample: allocation and deallocation") { SPDLOG_INFO("== Testing FieldSample class"); @@ -37,13 +39,9 @@ TEST_CASE("FieldSample: allocation and deallocation") { mxArray *populated_struct = mxCreateStructArray(2, (const mwSize *) struct_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); - mxSetField(populated_struct, 0, fieldnames[0], i_vector); mxArray *j_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); - mxSetField(populated_struct, 0, fieldnames[1], j_vector); mxArray *k_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxINT32_CLASS, mxREAL); - mxSetField(populated_struct, 0, fieldnames[2], k_vector); mxArray *n_vector = mxCreateNumericArray(2, (const mwSize *) vector_dims, mxDOUBLE_CLASS, mxREAL); - mxSetField(populated_struct, 0, fieldnames[3], n_vector); // 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... @@ -53,16 +51,28 @@ TEST_CASE("FieldSample: allocation and deallocation") { 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); + place_n_data[i] = 1. / (double) (i + 1); } + // set the vectors to be the field values + mxSetField(populated_struct, 0, fieldnames[0], i_vector); + mxSetField(populated_struct, 0, fieldnames[1], j_vector); + mxSetField(populated_struct, 0, fieldnames[2], k_vector); + mxSetField(populated_struct, 0, fieldnames[3], n_vector); // we should be able to construct things now REQUIRE_NOTHROW(FieldSample(populated_struct)); FieldSample fs(populated_struct); // check that the tensor attribute is the correct size const mwSize *dims = mxGetDimensions(fs.mx); - for(int i = 0; i < 4; i++) { + 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); + } // cleanup mxDestroyArray(populated_struct); // FieldSample destructor will deallocate fs.tensor correctly From e1a853159b1bcb7f15f228a80eb61c69cfbacf98 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 16:54:31 +0000 Subject: [PATCH 09/14] DetectorSensitivityArrays tests --- .../test_DetectorSensitivityArrays.cpp | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp 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..7744a0dd5 --- /dev/null +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -0,0 +1,36 @@ +/** + * @file test_DetectorSensitivityArrays.cpp + * @author William Graham (ccaegra@ucl.ac.uk) + * @brief Unit tests for the DetectorSensitivityArrays class and its subclasses + */ +#include +#include + +#include "arrays.h" +#include "globals.h" + +using namespace std; +using namespace tdms_math_constants; + +const double tol = 1e-16; + +TEST_CASE("DetectorSensitivityArrays: allocation and deallocation") { + SPDLOG_INFO("== Testing DetectorSensitivityArrays class"); + // 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 + for (int i = 0; i < n_rows; i++) { + for (int j =0; j < n_cols; j++) { + CHECK_NOTHROW(dsa.cm[i][j] = (double)i + IMAGINARY_UNIT * (double)j); + } + } + // destructor will then clear the memory +} From 769080f7a4f90a9b728b05841450fe909a418350 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Thu, 3 Nov 2022 17:00:56 +0000 Subject: [PATCH 10/14] try executing DSA fftw_plan --- .../tests/unit/array_tests/test_DetectorSensitivityArrays.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp index 7744a0dd5..9d6ce5816 100644 --- a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -32,5 +32,9 @@ TEST_CASE("DetectorSensitivityArrays: allocation and deallocation") { CHECK_NOTHROW(dsa.cm[i][j] = (double)i + IMAGINARY_UNIT * (double)j); } } + // 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)); + // destructor will then clear the memory } From 42c988bf0b491c2fad8843ca9cf1a1c134680677 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Fri, 4 Nov 2022 12:22:25 +0000 Subject: [PATCH 11/14] Reminder for me --- tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp index 9d6ce5816..8a6354a7e 100644 --- a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -35,6 +35,7 @@ TEST_CASE("DetectorSensitivityArrays: allocation and deallocation") { // 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)); + // https://en.wikipedia.org/wiki/Multidimensional_transform#Multidimensional_Fourier_transform <- choose entries accordingly // destructor will then clear the memory } From 1919e718b71d2fbd70ed309bc028a44586f05db2 Mon Sep 17 00:00:00 2001 From: Will Graham <32364977+willGraham01@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:32:37 +0000 Subject: [PATCH 12/14] Apply suggestions from code review Co-authored-by: Sam Cunliffe --- tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp index 66704c87d..8dc5ae16f 100644 --- a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp +++ b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp @@ -32,7 +32,7 @@ TEST_CASE("DispersiveMultiLayer: allocation and deallocation") { // For successful construction, we need to build a MATLAB struct with 9 fields // these are the fieldnames that are expected const int n_fields = 9; - const char *fieldnames[n_fields] = {"alpha", "beta", "gamma", "kappa_x", "kappa_y", + 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 From c2eb593dafa80357ac3caf3bd1e840a09f9efcd8 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 14 Nov 2022 15:57:07 +0000 Subject: [PATCH 13/14] Some SPDLOG cleanups, SECTION checks, etc --- tdms/tests/unit/array_tests/test_DTilde.cpp | 118 +++++++----- .../test_DetectorSensitivityArrays.cpp | 13 +- .../array_tests/test_DispersiveMultiLayer.cpp | 96 ++++----- .../unit/array_tests/test_FieldSample.cpp | 182 ++++++++++-------- .../array_tests/test_FrequencyVectors.cpp | 122 ++++++------ .../unit/array_tests/test_IncidentField.cpp | 116 +++++------ tdms/tests/unit/array_tests/test_Matrix.cpp | 39 ++-- tdms/tests/unit/array_tests/test_Tensor3D.cpp | 97 ++++++---- tdms/tests/unit/array_tests/test_Vector.cpp | 4 +- .../unit/array_tests/test_XYZTensor3D.cpp | 4 +- .../unit/array_tests/test_XYZVectors.cpp | 5 +- 11 files changed, 440 insertions(+), 356 deletions(-) diff --git a/tdms/tests/unit/array_tests/test_DTilde.cpp b/tdms/tests/unit/array_tests/test_DTilde.cpp index d09fde5fa..38cf46333 100644 --- a/tdms/tests/unit/array_tests/test_DTilde.cpp +++ b/tdms/tests/unit/array_tests/test_DTilde.cpp @@ -11,70 +11,82 @@ using namespace std; TEST_CASE("DTilde: allocation and deallocation") { - SPDLOG_INFO("== Testing DTilde class"); - // no custom constructor + DTilde dt; + bool no_information_stored; + int dims[2]; + mxArray *test_input; + // check no information is contained in the DTilde object - bool no_information_stored = - (dt.num_det_modes() == 0) && (!dt.x.has_elements()) && (!dt.y.has_elements()); - REQUIRE(no_information_stored); + 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 - const int empty_dims[2] = {0, 1}; - mxArray *empty_array = - mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); - // attempt assignment (2nd and 3rd args don't matter) - dt.initialise(empty_array, 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); - // cleanup the empty array mess we just made - mxDestroyArray(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 - int dims[2] = {2, 3}; - mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); - CHECK_THROWS_AS(dt.initialise(not_a_struct, 0, 0), runtime_error); - // cleanup - mxDestroyArray(not_a_struct); + 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 - const char *too_few_names[1] = {"field1"}; - const char *too_mny_names[3] = {"field1", "field2", "field3"}; - mxArray *struct_with_too_few_fields = - mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); - mxArray *struct_with_too_mny_fields = - mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); - CHECK_THROWS_AS(dt.initialise(struct_with_too_few_fields, 0, 0), runtime_error); - CHECK_THROWS_AS(dt.initialise(struct_with_too_mny_fields, 0, 0), runtime_error); - // cleanup - mxDestroyArray(struct_with_too_few_fields); - mxDestroyArray(struct_with_too_mny_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 - 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 - mxArray *example_struct = 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(example_struct, 0, fieldnames[i], field_array_ptrs[i]); + 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); } - // attempt to create a vector from this struct - REQUIRE_NOTHROW(dt.initialise(example_struct, 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 - mxDestroyArray(example_struct); + + // 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 index 8a6354a7e..90e0df09a 100644 --- a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -5,6 +5,9 @@ */ #include #include +#include + +#include #include "arrays.h" #include "globals.h" @@ -27,15 +30,15 @@ TEST_CASE("DetectorSensitivityArrays: allocation and deallocation") { 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++) { - CHECK_NOTHROW(dsa.cm[i][j] = (double)i + IMAGINARY_UNIT * (double)j); + 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)); - // https://en.wikipedia.org/wiki/Multidimensional_transform#Multidimensional_Fourier_transform <- choose entries accordingly - - // destructor will then clear the memory } diff --git a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp index 66704c87d..3093f47ae 100644 --- a/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp +++ b/tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp @@ -4,65 +4,73 @@ * @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") { - SPDLOG_INFO("== Testing DispersiveMultiLayer class"); + mxArray *matlab_input; // Constructor should throw runtime_error at not recieving struct // create a MATLAB pointer to something that isn't an array - int n_elements[2] = {2, 3}; - mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) n_elements, mxUINT8_CLASS, mxREAL); - CHECK_THROWS_AS(DispersiveMultiLayer(not_a_struct), runtime_error); - // cleanup - mxDestroyArray(not_a_struct); + 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 - const char* empty_fields[] = {}; - const int empty_dims[2] = {1,1}; - mxArray *empty_struct_pointer = mxCreateStructArray(2, (const mwSize*) empty_dims, 0, empty_fields); - CHECK_THROWS_AS(DispersiveMultiLayer(empty_struct_pointer), runtime_error); - mxDestroyArray(empty_struct_pointer); + 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 - 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}; - mxArray *useful_struct_pointer = 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(useful_struct_pointer, 0, fieldnames[i], field_array_ptrs[i]); + 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)); + } } - // we should now be able to create a DispersiveMultiLayer object - REQUIRE_NOTHROW(DispersiveMultiLayer(useful_struct_pointer)); - DispersiveMultiLayer dml(useful_struct_pointer); - // now check that the data has been correctly assigned - for (int i = 0; i < 5; i++) { - CHECK(dml.alpha[i] == (double) i); - CHECK(dml.beta[i] == (double) i); - CHECK(dml.gamma[i] == (double) i); - CHECK(dml.kappa.x[i] == (double) i); - CHECK(dml.kappa.y[i] == (double) i); - CHECK(dml.kappa.z[i] == (double) i); - CHECK(dml.sigma.x[i] == (double) i); - CHECK(dml.sigma.y[i] == (double) i); - CHECK(dml.sigma.z[i] == (double) i); - } - // cleanup - mxDestroyArray(useful_struct_pointer); + + // 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 index 85c57b1d7..15e4d9f64 100644 --- a/tdms/tests/unit/array_tests/test_FieldSample.cpp +++ b/tdms/tests/unit/array_tests/test_FieldSample.cpp @@ -12,94 +12,118 @@ using namespace std; const double tol = 1e-16; -TEST_CASE("FieldSample: allocation and deallocation") { - SPDLOG_INFO("== Testing FieldSample class"); +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 - const int empty_dims[2] = {0, 1}; - mxArray *empty_array = - mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); - // attempt assignment (2nd and 3rd args don't matter) - FieldSample empty_fs(empty_array); - // 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); - // cleanup the empty array mess we just made - mxDestroyArray(empty_array); + 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); + } - // should really test whether the inputs being the wrong size/shape/etc are bad, but equally this is pointless, as we're just checking against the same things - // as such, let's just skip to the correct constructor + // 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); + } - // expecting a struct with fields i, j, k, and n - const char *fieldnames[] = {"i", "j", "k", "n"}; - const int struct_dims[2] = {1, 1}; - const int number_of_vector_elements = 8; - const int vector_dims[2] = {1, number_of_vector_elements}; - // create the struct - mxArray *populated_struct = mxCreateStructArray(2, (const mwSize *) struct_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); + // 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); } - // set the vectors to be the field values - mxSetField(populated_struct, 0, fieldnames[0], i_vector); - mxSetField(populated_struct, 0, fieldnames[1], j_vector); - mxSetField(populated_struct, 0, fieldnames[2], k_vector); - mxSetField(populated_struct, 0, fieldnames[3], n_vector); - // we should be able to construct things now - REQUIRE_NOTHROW(FieldSample(populated_struct)); - FieldSample fs(populated_struct); - // 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); + 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); } - // 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); + + // 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); + } } - // cleanup - mxDestroyArray(populated_struct); - // FieldSample destructor will deallocate fs.tensor correctly // alternatively, we can pass in empty vectors to the constructor and it should create an empty 4D tensor - const int empty_vector_dims[2] = {1, 0}; - // create the struct - mxArray *struct_with_empty_vectors = mxCreateStructArray(2, (const mwSize *) struct_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(struct_with_empty_vectors, 0, fieldnames[0], empty_i_vector); - mxSetField(struct_with_empty_vectors, 0, fieldnames[1], empty_j_vector); - mxSetField(struct_with_empty_vectors, 0, fieldnames[2], empty_k_vector); - mxSetField(struct_with_empty_vectors, 0, fieldnames[3], empty_n_vector); - // we should be able to construct things now - REQUIRE_NOTHROW(FieldSample(struct_with_empty_vectors)); - FieldSample fs_from_empty_vectors(struct_with_empty_vectors); - // 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); + 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(struct_with_empty_vectors); + mxDestroyArray(matlab_input); } diff --git a/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp index 2aa68b6d1..fe6dd7e0f 100644 --- a/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp +++ b/tdms/tests/unit/array_tests/test_FrequencyVectors.cpp @@ -13,79 +13,81 @@ using namespace std; const double tol = 1e-16; TEST_CASE("FrequencyVectors: allocation and deallocation") { - SPDLOG_INFO("== Testing FrequencyVectors class"); - // there is no custom constructor for this class + FrequencyVectors fv; - // but the members should start as nullptrs + 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 - const int empty_dims[2] = {0, 1}; - mxArray *empty_array = - mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); - // attempt assignment - fv.initialise(empty_array); - // should still be unassigned vectors - not_assigned = (!fv.x.has_elements() && !fv.y.has_elements()); - REQUIRE(not_assigned); - // cleanup the empty array mess we just made - mxDestroyArray(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 - int dims[2] = {2, 3}; - mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); - CHECK_THROWS_AS(fv.initialise(not_a_struct), runtime_error); - // cleanup - mxDestroyArray(not_a_struct); + 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 - const char *too_few_names[1] = {"field1"}; - const char *too_mny_names[3] = {"field1", "field2", "field3"}; - mxArray *struct_with_too_few_fields = - mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); - mxArray *struct_with_too_mny_fields = - mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); - CHECK_THROWS_AS(fv.initialise(struct_with_too_few_fields), runtime_error); - CHECK_THROWS_AS(fv.initialise(struct_with_too_mny_fields), runtime_error); - // cleanup - mxDestroyArray(struct_with_too_few_fields); - mxDestroyArray(struct_with_too_mny_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 - const char *fieldnames[] = {"fx_vec", "fy_vec"}; - dims[0] = 1; - dims[1] = 1; - const int n_field_array_elements = 10; - const int field_array_dims[2] = {1, n_field_array_elements}; - // create the struct - mxArray *example_struct = 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)); + 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); } - mxSetField(example_struct, 0, fieldnames[i], field_array_ptrs[i]); - } - // attempt to create a vector from this struct - REQUIRE_NOTHROW(fv.initialise(example_struct)); - // 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(example_struct); + mxDestroyArray(matlab_input); } diff --git a/tdms/tests/unit/array_tests/test_IncidentField.cpp b/tdms/tests/unit/array_tests/test_IncidentField.cpp index 54401d005..ad5ade613 100644 --- a/tdms/tests/unit/array_tests/test_IncidentField.cpp +++ b/tdms/tests/unit/array_tests/test_IncidentField.cpp @@ -13,76 +13,78 @@ using namespace std; const double tol = 1e-16; TEST_CASE("IncidentField: allocation and deallocation") { - SPDLOG_INFO("== Testing IncidentField class"); + + mxArray *matlab_input; + int dims[2] = {1, 1}; + // construction should fail if given an empty array - const int empty_dims[2] = {0, 1}; - mxArray *empty_array = - mxCreateNumericArray(2, (const mwSize *) empty_dims, mxUINT8_CLASS, mxREAL); - // attempt construction (2nd and 3rd args don't matter) - REQUIRE_THROWS_AS(IncidentField(empty_array), runtime_error); - // cleanup the empty array mess we just made - mxDestroyArray(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 - int dims[2] = {2, 3}; - mxArray *not_a_struct = mxCreateNumericArray(2, (const mwSize *) dims, mxUINT8_CLASS, mxREAL); - CHECK_THROWS_AS(IncidentField(not_a_struct), runtime_error); - // cleanup - mxDestroyArray(not_a_struct); + 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 - const char *too_few_names[1] = {"field1"}; - const char *too_mny_names[3] = {"field1", "field2", "field3"}; - mxArray *struct_with_too_few_fields = - mxCreateStructArray(2, (const mwSize *) dims, 1, too_few_names); - mxArray *struct_with_too_mny_fields = - mxCreateStructArray(2, (const mwSize *) dims, 3, too_mny_names); - CHECK_THROWS_AS(IncidentField(struct_with_too_few_fields), runtime_error); - CHECK_THROWS_AS(IncidentField(struct_with_too_mny_fields), runtime_error); - // cleanup - mxDestroyArray(struct_with_too_few_fields); - mxDestroyArray(struct_with_too_mny_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 - const char *fieldnames[] = {"exi", "eyi"}; - dims[0] = 1; - dims[1] = 1; - 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 - mxArray *example_struct = 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++) { + 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)); - } + 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]); } - mxSetField(example_struct, 0, fieldnames[i], field_array_ptrs[i]); - } - // attempt to create a vector from this struct - REQUIRE_NOTHROW(IncidentField(example_struct)); - IncidentField i_field(example_struct); - // 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); + // 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(example_struct); + 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()); 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); From 164328af4bcb690446570b81e60680916fb67142 Mon Sep 17 00:00:00 2001 From: willGraham01 <1willgraham@gmail.com> Date: Mon, 14 Nov 2022 15:59:18 +0000 Subject: [PATCH 14/14] Remove SPDLOG name-dump --- .../tests/unit/array_tests/test_DetectorSensitivityArrays.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp index 90e0df09a..6403308af 100644 --- a/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp +++ b/tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp @@ -17,8 +17,8 @@ using namespace tdms_math_constants; const double tol = 1e-16; -TEST_CASE("DetectorSensitivityArrays: allocation and deallocation") { - SPDLOG_INFO("== Testing DetectorSensitivityArrays class"); +TEST_CASE("DetectorSensitivityArrays") { + // default constructor should set everything to nullptrs // destructor uses fftw destroy, which handles nullptrs itself DetectorSensitivityArrays empty_dsa;