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
6#include <vector>
7#include <limits>
8#include <random>
9#include <cmath>
10#include <memory>
11#include <cstdint>
12#include <type_traits>
13
17
19#include "utils.hpp"
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 const auto n_expected = expected.size();
111 ASSERT_EQ(n_expected, observed.size()) << "mismatch in vector length (" << context << ")";
112 for (I<decltype(n_expected)> 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 Value_>
123void compare_vectors(const std::vector<Value_>& expected, const std::vector<Value_>& observed, const char* context) {
124 compare_vectors(expected, observed, std::string(context));
125}
126
127template<typename Index_>
128SeedType create_seed(const Index_ NR, const Index_ NC, const TestAccessOptions& options) {
129 SeedType seed = static_cast<SeedType>(NR) * static_cast<SeedType>(NC);
130 seed += 13 * static_cast<SeedType>(options.use_row);
131 seed += 57 * static_cast<SeedType>(options.order);
132 seed += 101 * static_cast<SeedType>(options.jump);
133 return seed;
134}
135
136template<typename Index_>
137std::vector<Index_> simulate_test_access_sequence(const Index_ NR, const Index_ NC, const TestAccessOptions& options) {
138 std::vector<Index_> sequence;
139 const auto limit = (options.use_row ? NR : NC);
140
141 RngEngine rng(create_seed(NR, NC, options));
142 Index_ start = rng() % options.jump;
143 if (start < limit) {
144 while (1) {
145 sequence.push_back(start);
146 const Index_ remainder = limit - start;
147 // Make sure this comparison involves two unsigned integers to avoid GCC warnings.
148 if (sanisizer::is_less_than_or_equal(remainder, options.jump)) {
149 break;
150 }
151 start += options.jump;
152 }
153 }
154
155 if (options.order == TestAccessOrder::REVERSE) {
156 std::reverse(sequence.begin(), sequence.end());
157 } else if (options.order == TestAccessOrder::RANDOM) {
158 std::shuffle(sequence.begin(), sequence.end(), rng);
159 }
160
161 return sequence;
162}
163
164template<bool use_oracle_, typename Index_>
165tatami::MaybeOracle<use_oracle_, Index_> create_oracle(const std::vector<Index_>& sequence, const TestAccessOptions& options) {
166 if constexpr(use_oracle_) {
167 std::shared_ptr<tatami::Oracle<Index_> > oracle;
168 if (options.jump == 1 && options.order == TestAccessOrder::FORWARD) {
169 oracle.reset(new tatami::ConsecutiveOracle<Index_>(0, sanisizer::Cast(sequence.size())));
170 } else {
171 oracle.reset(new tatami::FixedViewOracle<Index_>(sequence.data(), sanisizer::Cast(sequence.size())));
172 }
173 return oracle;
174 } else {
175 return false;
176 }
177}
178
179template<bool use_oracle_, typename Value_, typename Index_, class SparseExpand_, typename ...Args_>
180void test_access_base(
181 const tatami::Matrix<Value_, Index_>& matrix,
182 const tatami::Matrix<Value_, Index_>& reference,
183 const TestAccessOptions& options,
184 const Index_ extent,
185 const SparseExpand_ sparse_expand,
186 Args_... args
187) {
188 const auto NR = matrix.nrow();
189 ASSERT_EQ(NR, reference.nrow());
190 const auto NC = matrix.ncol();
191 ASSERT_EQ(NC, reference.ncol());
192
193 auto refwork = (options.use_row ? reference.dense_row(args...) : reference.dense_column(args...));
194
195 auto sequence = simulate_test_access_sequence(NR, NC, options);
196 auto oracle = create_oracle<use_oracle_>(sequence, options);
197 auto pwork = tatami::new_extractor<false, use_oracle_>(&matrix, options.use_row, oracle, args...);
198
199 auto swork = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args...);
200 tatami::Options opt;
201 opt.sparse_extract_index = false;
202 auto swork_v = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
203 opt.sparse_extract_value = false;
204 auto swork_n = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
205 opt.sparse_extract_index = true;
206 auto swork_i = tatami::new_extractor<true, use_oracle_>(&matrix, options.use_row, oracle, args..., opt);
207
208 sanisizer::as_size_type<std::vector<Value_> >(extent);
209 std::vector<Value_> mat_dense_buffer(extent), ref_dense_buffer(extent), mat_vbuffer(extent), mat_vstore1(extent), mat_vstore2(extent);
210 sanisizer::as_size_type<std::vector<Index_> >(extent);
211 std::vector<Index_> mat_ibuffer(extent), mat_istore1(extent), mat_istore2(extent);
212 bool has_sparse = false;
213
214 for (const auto i : sequence) {
215 {
216 std::fill(ref_dense_buffer.begin(), ref_dense_buffer.end(), 0);
217 const auto ref_buf = ref_dense_buffer.data();
218 const auto ref_ptr = refwork->fetch(Fix(i), ref_buf);
219 tatami::copy_n(ref_ptr, extent, ref_buf);
220 }
221
222 // Dense retrieval.
223 {
224 std::fill(mat_dense_buffer.begin(), mat_dense_buffer.end(), 0);
225 const auto mat_buf = mat_dense_buffer.data();
226 const auto mat_ptr = [&]() {
227 if constexpr(use_oracle_) {
228 return pwork->fetch(mat_buf);
229 } else {
230 return pwork->fetch(Fix(i), mat_buf);
231 }
232 }();
233 tatami::copy_n(mat_ptr, extent, mat_buf);
234 compare_vectors(ref_dense_buffer, mat_dense_buffer, "dense retrieval");
235 }
236
237 // Sparse retrieval with both values and indices.
238 {
239 std::fill(mat_vbuffer.begin(), mat_vbuffer.end(), 0);
240 std::fill(mat_ibuffer.begin(), mat_ibuffer.end(), 0);
241 const auto vbuf = mat_vbuffer.data();
242 const auto ibuf = mat_ibuffer.data();
243
244 const auto observed = [&]() {
245 if constexpr(use_oracle_) {
246 return swork->fetch(vbuf, ibuf);
247 } else {
248 return swork->fetch(Fix(i), vbuf, ibuf);
249 }
250 }();
251 compare_vectors(ref_dense_buffer, sparse_expand(observed), "sparse retrieval");
252
253 if (!has_sparse && sanisizer::is_less_than(observed.number, extent)) {
254 has_sparse = true;
255 }
256
257 bool is_increasing = true;
258 for (I<decltype(observed.number)> i = 1; i < observed.number; ++i) {
259 if (observed.index[i] <= observed.index[i-1]) {
260 is_increasing = false;
261 break;
262 }
263 }
264 ASSERT_TRUE(is_increasing);
265
266 mat_vstore1.clear();
267 mat_vstore1.insert(mat_vstore1.end(), observed.value, observed.value + observed.number);
268 mat_istore1.clear();
269 mat_istore1.insert(mat_istore1.end(), observed.index, observed.index + observed.number);
270 }
271
272 // Sparse retrieval with indices only.
273 {
274 std::fill(mat_ibuffer.begin(), mat_ibuffer.end(), 0);
275 const auto ibuf = mat_ibuffer.data();
276
277 auto observed_i = [&]() {
278 if constexpr(use_oracle_) {
279 return swork_i->fetch(NULL, ibuf);
280 } else {
281 return swork_i->fetch(Fix(i), NULL, ibuf);
282 }
283 }();
284
285 ASSERT_TRUE(observed_i.value == NULL);
286 mat_istore2.clear();
287 mat_istore2.insert(mat_istore2.end(), observed_i.index, observed_i.index + observed_i.number);
288 ASSERT_EQ(mat_istore1, mat_istore2);
289 }
290
291 // Sparse retrieval with values only.
292 {
293 std::fill(mat_vbuffer.begin(), mat_vbuffer.end(), 0);
294 const auto vbuf = mat_vbuffer.data();
295
296 auto observed_v = [&]() {
297 if constexpr(use_oracle_) {
298 return swork_v->fetch(vbuf, NULL);
299 } else {
300 return swork_v->fetch(Fix(i), vbuf, NULL);
301 }
302 }();
303
304 ASSERT_TRUE(observed_v.index == NULL);
305 mat_vstore2.clear();
306 mat_vstore2.insert(mat_vstore2.end(), observed_v.value, observed_v.value + observed_v.number);
307 compare_vectors(mat_vstore1, mat_vstore2, "sparse retrieval with values only");
308 }
309
310 // Sparse retrieval with neither indices or values.
311 {
312 auto observed_n = [&]() {
313 if constexpr(use_oracle_) {
314 return swork_n->fetch(NULL, NULL);
315 } else {
316 return swork_n->fetch(Fix(i), NULL, NULL);
317 }
318 }();
319
320 ASSERT_TRUE(observed_n.value == NULL);
321 ASSERT_TRUE(observed_n.index == NULL);
322 ASSERT_EQ(mat_vstore1.size(), observed_n.number);
323 }
324 }
325
326 if (options.check_sparse && matrix.is_sparse()) {
327 EXPECT_TRUE(has_sparse);
328 }
329}
330
331template<bool use_oracle_, typename Value_, typename Index_>
333 const tatami::Matrix<Value_, Index_>& matrix,
334 const tatami::Matrix<Value_, Index_>& reference,
335 const TestAccessOptions& options
336) {
337 const Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
338 auto expected = sanisizer::create<std::vector<Value_> >(nsecondary);
339
340 test_access_base<use_oracle_>(
341 matrix,
342 reference,
343 options,
344 nsecondary,
345 [&](const tatami::SparseRange<Value_, Index_>& svec) -> const std::vector<Value_>& {
346 std::fill(expected.begin(), expected.end(), 0);
347 for (I<decltype(svec.number)> i = 0; i < svec.number; ++i) {
348 expected[svec.index[i]] = svec.value[i];
349 }
350 return expected;
351 }
352 );
353}
354
355template<bool use_oracle_, typename Value_, typename Index_>
357 const tatami::Matrix<Value_, Index_>& matrix,
358 const tatami::Matrix<Value_, Index_>& reference,
359 const double relative_start,
360 const double relative_length,
361 const TestAccessOptions& options
362) {
363 const Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
364 const Index_ start = nsecondary * relative_start;
365 const Index_ length = nsecondary * relative_length;
366 auto expected = sanisizer::create<std::vector<Value_> >(length);
367
368 test_access_base<use_oracle_>(
369 matrix,
370 reference,
371 options,
372 length,
373 [&](const tatami::SparseRange<Value_, Index_>& svec) -> const std::vector<Value_>& {
374 std::fill(expected.begin(), expected.end(), 0);
375 for (I<decltype(svec.number)> i = 0; i < svec.number; ++i) {
376 expected[svec.index[i] - start] = svec.value[i];
377 }
378 return expected;
379 },
380 start,
381 length
382 );
383}
384
385template<bool use_oracle_, typename Value_, typename Index_>
387 const tatami::Matrix<Value_, Index_>& matrix,
388 const tatami::Matrix<Value_, Index_>& reference,
389 const double relative_start,
390 const double probability,
391 const TestAccessOptions& options
392) {
393 const Index_ nsecondary = (options.use_row ? reference.ncol() : reference.nrow());
394 auto index_ptr = create_indexed_subset(
395 nsecondary,
396 relative_start,
397 probability,
398 static_cast<SeedType>(
399 create_seed(matrix.nrow(), matrix.ncol(), options)
400 + static_cast<SeedType>(999 * probability)
401 + static_cast<SeedType>(85 * relative_start)
402 )
403 );
404
405 const Index_ num_indices = index_ptr->size();
406 constexpr std::size_t placeholder = -1;
407 auto reposition = sanisizer::create<std::vector<std::size_t> >(nsecondary, placeholder);
408 {
409 const auto& indices = *index_ptr;
410 for (Index_ i = 0; i < num_indices; ++i) {
411 reposition[indices[i]] = i;
412 }
413 }
414
415 auto expected = sanisizer::create<std::vector<Value_> >(num_indices);
416
417 test_access_base<use_oracle_>(
418 matrix,
419 reference,
420 options,
421 num_indices,
422 [&](const tatami::SparseRange<Value_, Index_>& svec) -> const std::vector<Value_>& {
423 std::fill(expected.begin(), expected.end(), 0);
424 for (I<decltype(svec.number)> i = 0; i < svec.number; ++i) {
425 expected[reposition[svec.index[i]]] = svec.value[i];
426 }
427 return expected;
428 },
429 std::move(index_ptr)
430 );
431}
432
433}
450template<typename Value_, typename Index_>
452 const tatami::Matrix<Value_, Index_>& matrix,
453 const tatami::Matrix<Value_, Index_>& reference,
454 const TestAccessOptions& options
455) {
456 if (options.use_oracle) {
457 internal::test_full_access<true>(matrix, reference, options);
458 } else {
459 internal::test_full_access<false>(matrix, reference, options);
460 }
461}
462
481template<typename Value_, typename Index_>
483 const tatami::Matrix<Value_, Index_>& matrix,
484 const tatami::Matrix<Value_, Index_>& reference,
485 double relative_start,
486 double relative_length,
487 const TestAccessOptions& options
488) {
489 if (options.use_oracle) {
490 internal::test_block_access<true>(matrix, reference, relative_start, relative_length, options);
491 } else {
492 internal::test_block_access<false>(matrix, reference, relative_start, relative_length, options);
493 }
494}
495
514template<typename Value_, typename Index_>
516 const tatami::Matrix<Value_, Index_>& matrix,
517 const tatami::Matrix<Value_, Index_>& reference,
518 double relative_start,
519 double probability,
520 const TestAccessOptions& options
521) {
522 if (options.use_oracle) {
523 internal::test_indexed_access<true>(matrix, reference, relative_start, probability, options);
524 } else {
525 internal::test_indexed_access<false>(matrix, reference, relative_start, probability, options);
526 }
527}
528
541template<typename Value_, typename Index_>
543 TestAccessOptions options;
544 options.use_row = false;
545 test_full_access(matrix, reference, options);
546}
547
560template<typename Value_, typename Index_>
562 TestAccessOptions options;
563 options.use_row = false;
564 test_full_access(matrix, reference, options);
565}
566
567}
568
569#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.
Utilities for testing tatami libraries.
Definition create_indexed_subset.hpp:16
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:482
tatami::VectorPtr< Index_ > create_indexed_subset(const Index_ extent, const double relative_start, const double probability, const SeedType seed)
Definition create_indexed_subset.hpp:36
std::mt19937_64 RngEngine
Definition utils.hpp:34
RngEngine::result_type SeedType
Definition utils.hpp:39
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:515
void test_simple_row_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference)
Definition test_access.hpp:561
void test_simple_column_access(const tatami::Matrix< Value_, Index_ > &matrix, const tatami::Matrix< Value_, Index_ > &reference)
Definition test_access.hpp:542
TestAccessOptions convert_test_access_options(const StandardTestAccessOptions &x)
Definition test_access.hpp:81
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:451
auto new_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, MaybeOracle< oracle_, Index_ > oracle, Args_ &&... args)
typename std::conditional< oracle_, std::shared_ptr< const Oracle< Index_ > >, bool >::type MaybeOracle
Value_ * copy_n(const Value_ *const input, const Size_ n, Value_ *const output)
bool sparse_extract_index
bool sparse_extract_value
const Value_ * value
const Index_ * index
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
Miscellaneous utilities.