tatami_test
Utilities for testing tatami libraries
Loading...
Searching...
No Matches
test_access.hpp
Go to the documentation of this file.
1#ifndef TATAMI_TEST_TEST_ACCESS_HPP
2#define TATAMI_TEST_TEST_ACCESS_HPP
3
4#include <gtest/gtest.h>
5
9
10#include "fetch.hpp"
12
13#include <vector>
14#include <limits>
15#include <random>
16#include <cmath>
17#include <memory>
18#include <cstdint>
19#include <type_traits>
20
26namespace tatami_test {
27
35enum class TestAccessOrder : char { FORWARD, REVERSE, RANDOM };
36
44 bool use_oracle = false;
45
50 bool use_row = true;
51
55 TestAccessOrder order = TestAccessOrder::FORWARD;
56
60 int jump = 1;
61
65 bool check_sparse = true;
66};
67
72typedef std::tuple<bool, bool, TestAccessOrder, int> StandardTestAccessOptions;
73
82 TestAccessOptions output;
83 output.use_row = std::get<0>(x);
84 output.use_oracle = std::get<1>(x);
85 output.order = std::get<2>(x);
86 output.jump = std::get<3>(x);
87 return output;
88}
89
95 return ::testing::Combine(
96 ::testing::Values(true, false), /* whether to access the rows. */
97 ::testing::Values(true, false), /* whether to use an oracle. */
98 ::testing::Values(TestAccessOrder::FORWARD, TestAccessOrder::REVERSE, TestAccessOrder::RANDOM), /* access order. */
99 ::testing::Values(1, 3) /* jump between rows/columns. */
100 );
101}
102
106namespace internal {
107
108template<typename Value_>
109void compare_vectors(const std::vector<Value_>& expected, const std::vector<Value_>& observed, const std::string& context) {
110 size_t n_expected = expected.size();
111 ASSERT_EQ(n_expected, observed.size()) << "mismatch in vector length (" << context << ")";
112 for (size_t i = 0; i < n_expected; ++i) {
113 auto expected_val = expected[i], observed_val = observed[i];
114 if (std::isnan(expected_val)) {
115 EXPECT_EQ(std::isnan(expected_val), std::isnan(observed_val)) << "mismatching NaNs at position " << i << " (" << context << ")";
116 } else {
117 EXPECT_EQ(expected_val, observed_val) << "different values at position " << i << " (" << context << ")";
118 }
119 }
120}
121
122template<typename Index_>
123uint64_t create_seed(Index_ NR, Index_ NC, const TestAccessOptions& options) {
124 uint64_t seed = static_cast<uint64_t>(NR) * static_cast<uint64_t>(NC);
125 seed += 13 * static_cast<uint64_t>(options.use_row);
126 seed += 57 * static_cast<uint64_t>(options.order);
127 seed += 101 * static_cast<uint64_t>(options.jump);
128 return seed;
129}
130
131template<typename Index_>
132std::vector<Index_> simulate_test_access_sequence(Index_ NR, Index_ NC, const TestAccessOptions& options) {
133 std::vector<Index_> sequence;
134 auto limit = (options.use_row ? NR : NC);
135
136 std::mt19937_64 rng(create_seed(NR, NC, options));
137 Index_ start = rng() % options.jump;
138 if (start < limit) {
139 while (1) {
140 sequence.push_back(start);
141 Index_ remainder = limit - start;
142 // Make sure this comparison involves two unsigned integers to avoid GCC warnings.
143 if (static_cast<typename std::make_unsigned<Index_>::type>(remainder) <= static_cast<typename std::make_unsigned<int>::type>(options.jump)) {
144 break;
145 }
146 start += options.jump;
147 }
148 }
149
150 if (options.order == TestAccessOrder::REVERSE) {
151 std::reverse(sequence.begin(), sequence.end());
152 } else if (options.order == TestAccessOrder::RANDOM) {
153 std::shuffle(sequence.begin(), sequence.end(), rng);
154 }
155
156 return sequence;
157}
158
159template<bool use_oracle_, typename Index_>
160tatami::MaybeOracle<use_oracle_, Index_> create_oracle(const std::vector<Index_>& sequence, const TestAccessOptions& options) {
161 if constexpr(use_oracle_) {
162 std::shared_ptr<tatami::Oracle<Index_> > oracle;
163 if (options.jump == 1 && options.order == TestAccessOrder::FORWARD) {
164 oracle.reset(new tatami::ConsecutiveOracle<Index_>(0, sequence.size()));
165 } else {
166 oracle.reset(new tatami::FixedViewOracle<Index_>(sequence.data(), sequence.size()));
167 }
168 return oracle;
169 } else {
170 return false;
171 }
172}
173
174template<bool use_oracle_, typename Value_, typename Index_, class SparseExpand_, typename ...Args_>
175void test_access_base(
176 const tatami::Matrix<Value_, Index_>& matrix,
177 const tatami::Matrix<Value_, Index_>& reference,
178 const TestAccessOptions& options,
179 Index_ extent,
180 SparseExpand_ sparse_expand,
181 Args_... args)
182{
183 auto NR = matrix.nrow();
184 ASSERT_EQ(NR, reference.nrow());
185 auto NC = matrix.ncol();
186 ASSERT_EQ(NC, reference.ncol());
187
188 auto refwork = (options.use_row ? reference.dense_row(args...) : reference.dense_column(args...));
189
190 auto sequence = simulate_test_access_sequence(NR, NC, options);
191 auto oracle = create_oracle<use_oracle_>(sequence, options);
192
193 auto pwork = tatami::new_extractor<false, use_oracle_>(&matrix, options.use_row, oracle, args...);
194 auto swork = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args...);
195
196 tatami::Options opt;
197 opt.sparse_extract_index = false;
198 auto swork_v = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
199
200 opt.sparse_extract_value = false;
201 auto swork_n = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
202
203 opt.sparse_extract_index = true;
204 auto swork_i = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
205
206 size_t sparse_counter = 0;
207
208 // Looping over rows/columns and checking extraction against the reference.
209 for (auto i : sequence) {
210 auto expected = fetch(*refwork, i, extent);
211
212 // Checking dense retrieval first.
213 {
214 auto observed = [&]() {
215 if constexpr(use_oracle_) {
216 return fetch(*pwork, extent);
217 } else {
218 return fetch(*pwork, i, extent);
219 }
220 }();
221 compare_vectors(expected, observed, "dense retrieval");
222 }
223
224 // Various flavors of sparse retrieval.
225 {
226 auto observed = [&]() {
227 if constexpr(use_oracle_) {
228 return fetch(*swork, extent);
229 } else {
230 return fetch(*swork, i, extent);
231 }
232 }();
233 compare_vectors(expected, sparse_expand(observed), "sparse retrieval");
234
235 sparse_counter += observed.value.size();
236 {
237 bool is_increasing = true;
238 for (size_t i = 1; i < observed.index.size(); ++i) {
239 if (observed.index[i] <= observed.index[i-1]) {
240 is_increasing = false;
241 break;
242 }
243 }
244 ASSERT_TRUE(is_increasing);
245 }
246
247 std::vector<Index_> indices(extent);
248 auto observed_i = [&]() {
249 if constexpr(use_oracle_) {
250 return swork_i->fetch(NULL, indices.data());
251 } else {
252 return swork_i->fetch(i, NULL, indices.data());
253 }
254 }();
255 ASSERT_TRUE(observed_i.value == NULL);
256 tatami::copy_n(observed_i.index, observed_i.number, indices.data());
257 indices.resize(observed_i.number);
258 ASSERT_EQ(observed.index, indices);
259
260 std::vector<Value_> values(extent);
261 auto observed_v = [&]() {
262 if constexpr(use_oracle_) {
263 return swork_v->fetch(values.data(), NULL);
264 } else {
265 return swork_v->fetch(i, values.data(), NULL);
266 }
267 }();
268 ASSERT_TRUE(observed_v.index == NULL);
269 tatami::copy_n(observed_v.value, observed_v.number, values.data());
270 values.resize(observed_v.number);
271 compare_vectors(values, observed.value, "sparse retrieval with values only");
272
273 auto observed_n = [&]() {
274 if constexpr(use_oracle_) {
275 return swork_n->fetch(NULL, NULL);
276 } else {
277 return swork_n->fetch(i, NULL, NULL);
278 }
279 }();
280 ASSERT_TRUE(observed_n.value == NULL);
281 ASSERT_TRUE(observed_n.index == NULL);
282 ASSERT_EQ(observed.value.size(), observed_n.number);
283 }
284 }
285
286 if (options.check_sparse && matrix.is_sparse()) {
287 EXPECT_TRUE(sparse_counter < static_cast<size_t>(NR) * static_cast<size_t>(NC));
288 }
289}
290
291template<bool use_oracle_, typename Value_, typename Index_>
293 const tatami::Matrix<Value_, Index_>& matrix,
294 const tatami::Matrix<Value_, Index_>& reference,
295 const TestAccessOptions& options)
296{
297 Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
298 test_access_base<use_oracle_>(
299 matrix,
300 reference,
301 options,
302 nsecondary,
303 [&](const auto& svec) -> auto {
304 std::vector<Value_> output(nsecondary);
305 size_t nnz = svec.index.size();
306 for (size_t i = 0; i < nnz; ++i) {
307 output[svec.index[i]] = svec.value[i];
308 }
309 return output;
310 }
311 );
312}
313
314template<bool use_oracle_, typename Value_, typename Index_>
316 const tatami::Matrix<Value_, Index_>& matrix,
317 const tatami::Matrix<Value_, Index_>& reference,
318 double relative_start,
319 double relative_length,
320 const TestAccessOptions& options)
321{
322 Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
323 Index_ start = nsecondary * relative_start;
324 Index_ length = nsecondary * relative_length;
325 test_access_base<use_oracle_>(
326 matrix,
327 reference,
328 options,
329 length,
330 [&](const auto& svec) -> auto {
331 std::vector<Value_> output(length);
332 size_t nnz = svec.index.size();
333 for (size_t i = 0; i < nnz; ++i) {
334 output[svec.index[i] - start] = svec.value[i];
335 }
336 return output;
337 },
338 start,
339 length
340 );
341}
342
343template<bool use_oracle_, typename Value_, typename Index_>
345 const tatami::Matrix<Value_, Index_>& matrix,
346 const tatami::Matrix<Value_, Index_>& reference,
347 double relative_start,
348 double probability,
349 const TestAccessOptions& options)
350{
351 Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
352 auto index_ptr = create_indexed_subset(
353 nsecondary,
354 relative_start,
355 probability,
356 create_seed(matrix.nrow(), matrix.ncol(), options) + 999 * probability + 85 * relative_start
357 );
358
359 Index_ num_indices = index_ptr->size();
360 std::vector<size_t> reposition(nsecondary, -1);
361 {
362 const auto& indices = *index_ptr;
363 for (Index_ i = 0; i < num_indices; ++i) {
364 reposition[indices[i]] = i;
365 }
366 }
367
368 test_access_base<use_oracle_>(
369 matrix,
370 reference,
371 options,
372 num_indices,
373 [&](const auto& svec) -> auto {
374 std::vector<Value_> expected(num_indices);
375 size_t nnz = svec.index.size();
376 for (size_t i = 0; i < nnz; ++i) {
377 expected[reposition[svec.index[i]]] = svec.value[i];
378 }
379 return expected;
380 },
381 std::move(index_ptr)
382 );
383}
384
385}
402template<typename Value_, typename Index_>
404 const tatami::Matrix<Value_, Index_>& matrix,
405 const tatami::Matrix<Value_, Index_>& reference,
406 const TestAccessOptions& options)
407{
408 if (options.use_oracle) {
409 internal::test_full_access<true>(matrix, reference, options);
410 } else {
411 internal::test_full_access<false>(matrix, reference, options);
412 }
413}
414
433template<typename Value_, typename Index_>
435 const tatami::Matrix<Value_, Index_>& matrix,
436 const tatami::Matrix<Value_, Index_>& reference,
437 double relative_start,
438 double relative_length,
439 const TestAccessOptions& options)
440{
441 if (options.use_oracle) {
442 internal::test_block_access<true>(matrix, reference, relative_start, relative_length, options);
443 } else {
444 internal::test_block_access<false>(matrix, reference, relative_start, relative_length, options);
445 }
446}
447
466template<typename Value_, typename Index_>
468 const tatami::Matrix<Value_, Index_>& matrix,
469 const tatami::Matrix<Value_, Index_>& reference,
470 double relative_start,
471 double probability,
472 const TestAccessOptions& options)
473{
474 if (options.use_oracle) {
475 internal::test_indexed_access<true>(matrix, reference, relative_start, probability, options);
476 } else {
477 internal::test_indexed_access<false>(matrix, reference, relative_start, probability, options);
478 }
479}
480
493template<typename Value_, typename Index_>
495 TestAccessOptions options;
496 options.use_row = false;
497 test_full_access(matrix, reference, options);
498}
499
512template<typename Value_, typename Index_>
514 TestAccessOptions options;
515 options.use_row = false;
516 test_full_access(matrix, reference, options);
517}
518
519}
520
521#endif
std::unique_ptr< MyopicDenseExtractor< Value_, Index_ > > dense_row(const Options &opt) const
virtual Index_ ncol() const=0
std::unique_ptr< MyopicDenseExtractor< Value_, Index_ > > dense_column(const Options &opt) const
virtual Index_ nrow() const=0
virtual bool is_sparse() const=0
Create an indexed subset of dimension elements.
Fetch a row/column into a vector.
Utilities for testing tatami libraries.
Definition create_indexed_subset.hpp:15
void test_block_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference, double relative_start, double relative_length, const TestAccessOptions &options)
Definition test_access.hpp:434
std::tuple< bool, bool, TestAccessOrder, int > StandardTestAccessOptions
Definition test_access.hpp:72
TestAccessOrder
Definition test_access.hpp:35
void test_indexed_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference, double relative_start, double probability, const TestAccessOptions &options)
Definition test_access.hpp:467
void test_simple_row_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference)
Definition test_access.hpp:513
void test_simple_column_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference)
Definition test_access.hpp:494
tatami::VectorPtr< Index_ > create_indexed_subset(Index_ extent, double relative_start, double probability, uint64_t seed)
Definition create_indexed_subset.hpp:35
TestAccessOptions convert_test_access_options(const StandardTestAccessOptions &x)
Definition test_access.hpp:81
std::vector< Value_ > fetch(tatami::MyopicDenseExtractor< Value_, Index_ > &ext, Index_ i, size_t number)
Definition fetch.hpp:46
auto standard_test_access_options_combinations()
Definition test_access.hpp:94
void test_full_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference, const TestAccessOptions &options)
Definition test_access.hpp:403
typename std::conditional< oracle_, std::shared_ptr< const Oracle< Index_ > >, bool >::type MaybeOracle
Value_ * copy_n(const Value_ *input, Size_ n, Value_ *output)
auto new_extractor(const Matrix< Value_, Index_ > &matrix, bool row, MaybeOracle< oracle_, Index_ > oracle, Args_ &&... args)
bool sparse_extract_index
bool sparse_extract_value
Options for test_full_access() and friends.
Definition test_access.hpp:40
bool use_oracle
Definition test_access.hpp:44
int jump
Definition test_access.hpp:60
bool use_row
Definition test_access.hpp:50
bool check_sparse
Definition test_access.hpp:65
TestAccessOrder order
Definition test_access.hpp:55