Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More array.h and field.h tests #159

Merged
merged 16 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions tdms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
92 changes: 92 additions & 0 deletions tdms/tests/unit/array_tests/test_DTilde.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @file test_DTilde.cpp
* @author William Graham ([email protected])
* @brief Unit tests for the DTilde class
*/
#include <catch2/catch_test_macros.hpp>
#include <spdlog/spdlog.h>

#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);
}
44 changes: 44 additions & 0 deletions tdms/tests/unit/array_tests/test_DetectorSensitivityArrays.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @file test_DetectorSensitivityArrays.cpp
* @author William Graham ([email protected])
* @brief Unit tests for the DetectorSensitivityArrays class and its subclasses
*/
#include <catch2/catch_test_macros.hpp>
#include <spdlog/spdlog.h>
#include <fftw3.h>

#include <cmath>

#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));
}
76 changes: 76 additions & 0 deletions tdms/tests/unit/array_tests/test_DispersiveMultiLayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @file test_DispersiveMultiLayer.cpp
* @author William Graham ([email protected])
* @brief Tests for the DispersiveMultiLayer class and its subclasses
*/
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <spdlog/spdlog.h>

#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);
}
129 changes: 129 additions & 0 deletions tdms/tests/unit/array_tests/test_FieldSample.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* @file test_FieldSample.cpp
* @author William Graham ([email protected])
* @brief Unit tests for the FieldSample class and its subclasses
*/
#include <catch2/catch_test_macros.hpp>
#include <spdlog/spdlog.h>

#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);
}
Loading