tatami_hdf5
tatami bindings for HDF5-backed matrices
Loading...
Searching...
No Matches
DenseMatrix.hpp
Go to the documentation of this file.
1#ifndef TATAMI_HDF5_DENSE_MATRIX_HPP
2#define TATAMI_HDF5_DENSE_MATRIX_HPP
3
4#include "H5Cpp.h"
5
6#include <string>
7#include <type_traits>
8#include <cmath>
9#include <vector>
10
11#include "serialize.hpp"
12#include "utils.hpp"
13#include "tatami_chunked/tatami_chunked.hpp"
14
21namespace tatami_hdf5 {
22
36 size_t maximum_cache_size = 100000000;
37
44};
45
49namespace DenseMatrix_internal {
50
51// All HDF5-related members.
52struct Components {
53 H5::H5File file;
54 H5::DataSet dataset;
55 H5::DataSpace dataspace;
56 H5::DataSpace memspace;
57};
58
59template<typename Index_, typename OutputValue_>
60void extract_block(bool h5_row_is_target, Index_ cache_start, Index_ cache_length, Index_ block_start, Index_ block_length, OutputValue_* buffer, Components& comp) {
61 hsize_t offset[2];
62 hsize_t count[2];
63
64 int target_dim = 1 - h5_row_is_target;
65 offset[target_dim] = cache_start;
66 count[target_dim] = cache_length;
67
68 int non_target_dim = h5_row_is_target;
69 offset[non_target_dim] = block_start;
70 count[non_target_dim] = block_length;
71 comp.dataspace.selectHyperslab(H5S_SELECT_SET, count, offset);
72
73 // HDF5 is a lot faster when the memspace and dataspace match in dimensionality.
74 // Presumably there is some shuffling that happens inside when dimensions don't match.
75 comp.memspace.setExtentSimple(2, count);
76 comp.memspace.selectAll();
77
78 comp.dataset.read(buffer, define_mem_type<OutputValue_>(), comp.memspace, comp.dataspace);
79}
80
81template<typename Index_, typename OutputValue_>
82void extract_indices(bool h5_row_is_target, Index_ cache_start, Index_ cache_length, const std::vector<Index_>& indices, OutputValue_* buffer, Components& comp) {
83 hsize_t offset[2];
84 hsize_t count[2];
85
86 int target_dim = 1 - h5_row_is_target;
87 offset[target_dim] = cache_start;
88 count[target_dim] = cache_length;
89
90 int non_target_dim = h5_row_is_target;
91
92 // Take slices across the current chunk for each index. This should be okay if consecutive,
93 // but hopefully they've fixed the problem with non-consecutive slices in:
94 // https://forum.hdfgroup.org/t/union-of-non-consecutive-hyperslabs-is-very-slow/5062
95 comp.dataspace.selectNone();
97 indices.data(),
98 indices.size(),
99 [&](Index_ start, Index_ length) -> void {
100 offset[non_target_dim] = start;
101 count[non_target_dim] = length;
102 comp.dataspace.selectHyperslab(H5S_SELECT_OR, count, offset);
103 }
104 );
105
106 // Again, matching the dimensionality.
107 count[non_target_dim] = indices.size();
108 comp.memspace.setExtentSimple(2, count);
109 comp.memspace.selectAll();
110
111 comp.dataset.read(buffer, define_mem_type<OutputValue_>(), comp.memspace, comp.dataspace);
112}
113
114/********************
115 *** Core classes ***
116 ********************/
117
118// We store the Components in a pointer so that we can serialize their
119// construction and destruction.
120
121inline void initialize(const std::string& file_name, const std::string& dataset_name, std::unique_ptr<Components>& h5comp) {
122 serialize([&]() -> void {
123 h5comp.reset(new Components);
124
125 // Turn off HDF5's caching, as we'll be handling that. This allows us
126 // to parallelize extractions without locking when the data has already
127 // been loaded into memory; if we just used HDF5's cache, we would have
128 // to lock on every extraction, given the lack of thread safety.
129 H5::FileAccPropList fapl(H5::FileAccPropList::DEFAULT.getId());
130 fapl.setCache(0, 0, 0, 0);
131
132 h5comp->file.openFile(file_name, H5F_ACC_RDONLY, fapl);
133 h5comp->dataset = h5comp->file.openDataSet(dataset_name);
134 h5comp->dataspace = h5comp->dataset.getSpace();
135 });
136}
137
138inline void destroy(std::unique_ptr<Components>& h5comp) {
139 serialize([&]() -> void {
140 h5comp.reset();
141 });
142}
143
144template<bool oracle_, bool by_h5_row_, typename Index_>
145class SoloCore {
146public:
147 SoloCore(
148 const std::string& file_name,
149 const std::string& dataset_name,
150 [[maybe_unused]] tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats, // only listed here for compatibility with the other constructors.
152 [[maybe_unused]] const tatami_chunked::SlabCacheStats& slab_stats) :
153 my_oracle(std::move(oracle))
154 {
155 initialize(file_name, dataset_name, my_h5comp);
156 }
157
158 ~SoloCore() {
159 destroy(my_h5comp);
160 }
161
162private:
163 std::unique_ptr<Components> my_h5comp;
165 typename std::conditional<oracle_, size_t, bool>::type my_counter = 0;
166
167public:
168 template<typename Value_>
169 const Value_* fetch_block(Index_ i, Index_ block_start, Index_ block_length, Value_* buffer) {
170 if constexpr(oracle_) {
171 i = my_oracle->get(my_counter++);
172 }
173 serialize([&]() -> void {
174 extract_block(by_h5_row_, i, static_cast<Index_>(1), block_start, block_length, buffer, *my_h5comp);
175 });
176 return buffer;
177 }
178
179 template<typename Value_>
180 const Value_* fetch_indices(Index_ i, const std::vector<Index_>& indices, Value_* buffer) {
181 if constexpr(oracle_) {
182 i = my_oracle->get(my_counter++);
183 }
184 serialize([&]() -> void {
185 extract_indices(by_h5_row_, i, static_cast<Index_>(1), indices, buffer, *my_h5comp);
186 });
187 return buffer;
188 }
189};
190
191template<bool by_h5_row_, typename Index_, typename CachedValue_>
192class MyopicCore {
193public:
194 MyopicCore(
195 const std::string& file_name,
196 const std::string& dataset_name,
197 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
198 [[maybe_unused]] tatami::MaybeOracle<false, Index_>, // for consistency with the oracular version.
199 const tatami_chunked::SlabCacheStats& slab_stats) :
200 my_dim_stats(std::move(target_dim_stats)),
201 my_factory(slab_stats),
202 my_cache(slab_stats.max_slabs_in_cache)
203 {
204 initialize(file_name, dataset_name, my_h5comp);
205 if constexpr(!by_h5_row_) {
206 my_transposition_buffer.resize(slab_stats.slab_size_in_elements);
207 }
208 }
209
210 ~MyopicCore() {
211 destroy(my_h5comp);
212 }
213
214private:
215 std::unique_ptr<Components> my_h5comp;
216 tatami_chunked::ChunkDimensionStats<Index_> my_dim_stats;
217
218 tatami_chunked::DenseSlabFactory<CachedValue_> my_factory;
219 typedef typename decltype(my_factory)::Slab Slab;
220 tatami_chunked::LruSlabCache<Index_, Slab> my_cache;
221
222 typename std::conditional<by_h5_row_, bool, std::vector<CachedValue_> >::type my_transposition_buffer;
223
224private:
225 template<typename Value_, class Extract_>
226 void fetch_raw(Index_ i, Value_* buffer, size_t non_target_length, Extract_ extract) {
227 Index_ chunk = i / my_dim_stats.chunk_length;
228 Index_ index = i % my_dim_stats.chunk_length;
229
230 const auto& info = my_cache.find(
231 chunk,
232 /* create = */ [&]() -> Slab {
233 return my_factory.create();
234 },
235 /* populate = */ [&](Index_ id, Slab& contents) -> void {
236 auto curdim = tatami_chunked::get_chunk_length(my_dim_stats, id);
237
238 if constexpr(by_h5_row_) {
239 serialize([&]() -> void {
240 extract(id * my_dim_stats.chunk_length, curdim, contents.data);
241 });
242
243 } else {
244 // Transposing the data for easier retrieval, but only once the lock is released.
245 auto tptr = my_transposition_buffer.data();
246 serialize([&]() -> void {
247 extract(id * my_dim_stats.chunk_length, curdim, tptr);
248 });
249 tatami::transpose(tptr, non_target_length, curdim, contents.data);
250 }
251 }
252 );
253
254 auto ptr = info.data + non_target_length * static_cast<size_t>(index); // cast to size_t to avoid overflow
255 std::copy_n(ptr, non_target_length, buffer);
256 }
257
258public:
259 template<typename Value_>
260 const Value_* fetch_block(Index_ i, Index_ block_start, Index_ block_length, Value_* buffer) {
261 fetch_raw(
262 i,
263 buffer,
264 block_length,
265 [&](Index_ start, Index_ length, CachedValue_* buf) -> void {
266 extract_block(by_h5_row_, start, length, block_start, block_length, buf, *my_h5comp);
267 }
268 );
269 return buffer;
270 }
271
272 template<typename Value_>
273 const Value_* fetch_indices(Index_ i, const std::vector<Index_>& indices, Value_* buffer) {
274 fetch_raw(
275 i,
276 buffer,
277 indices.size(),
278 [&](Index_ start, Index_ length, CachedValue_* buf) -> void {
279 extract_indices(by_h5_row_, start, length, indices, buf, *my_h5comp);
280 }
281 );
282 return buffer;
283 }
284};
285
286// This class performs oracular dense extraction when each target dimension element is a row in the HDF5 matrix.
287// No transposition is required and we can achieve some optimizations with the HDF5 library call.
288template<typename Index_, typename CachedValue_>
289class OracularCoreNormal {
290public:
291 OracularCoreNormal(
292 const std::string& file_name,
293 const std::string& dataset_name,
294 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
296 const tatami_chunked::SlabCacheStats& slab_stats) :
297 my_dim_stats(std::move(target_dim_stats)),
298 my_cache(std::move(oracle), slab_stats.max_slabs_in_cache),
299 my_slab_size(slab_stats.slab_size_in_elements),
300 my_memory_pool(slab_stats.max_slabs_in_cache * my_slab_size)
301 {
302 initialize(file_name, dataset_name, my_h5comp);
303 }
304
305 ~OracularCoreNormal() {
306 destroy(my_h5comp);
307 }
308
309private:
310 std::unique_ptr<Components> my_h5comp;
311 tatami_chunked::ChunkDimensionStats<Index_> my_dim_stats;
312
313 struct Slab {
314 size_t offset;
315 };
316
317 tatami_chunked::OracularSlabCache<Index_, Index_, Slab, true> my_cache;
318 size_t my_slab_size;
319 std::vector<CachedValue_> my_memory_pool;
320 size_t my_offset = 0;
321
322private:
323 template<class Function_>
324 static void sort_by_field(std::vector<std::pair<Index_, Slab*> >& indices, Function_ field) {
325 auto comp = [&field](const std::pair<Index_, Slab*>& l, const std::pair<Index_, Slab*>& r) -> bool {
326 return field(l) < field(r);
327 };
328 if (!std::is_sorted(indices.begin(), indices.end(), comp)) {
329 std::sort(indices.begin(), indices.end(), comp);
330 }
331 }
332
333 template<typename Value_, class Unionize_>
334 void fetch_raw([[maybe_unused]] Index_ i, Value_* buffer, size_t non_target_length, Unionize_ unionize) {
335 auto info = my_cache.next(
336 /* identify = */ [&](Index_ current) -> std::pair<Index_, Index_> {
337 return std::pair<Index_, Index_>(current / my_dim_stats.chunk_length, current % my_dim_stats.chunk_length);
338 },
339 /* create = */ [&]() -> Slab {
340 Slab output;
341 output.offset = my_offset;
342 my_offset += my_slab_size;
343 return output;
344 },
345 /* populate = */ [&](std::vector<std::pair<Index_, Slab*> >& chunks, std::vector<std::pair<Index_, Slab*> >& to_reuse) -> void {
346 // Defragmenting the existing chunks. We sort by offset to make
347 // sure that we're not clobbering in-use slabs during the copy().
348 sort_by_field(to_reuse, [](const std::pair<Index_, Slab*>& x) -> size_t { return x.second->offset; });
349
350 auto dest = my_memory_pool.data();
351 size_t running_offset = 0;
352 for (auto& x : to_reuse) {
353 auto& cur_offset = x.second->offset;
354 if (cur_offset != running_offset) {
355 std::copy_n(dest + cur_offset, my_slab_size, dest + running_offset);
356 cur_offset = running_offset;
357 }
358 running_offset += my_slab_size;
359 }
360
361 // Collapsing runs of consecutive hyperslabs into a single hyperslab;
362 // otherwise, taking hyperslab unions. This allows a single HDF5 call
363 // to populate the contiguous memory pool that we made available after
364 // defragmentation; then we just update the slab pointers to refer
365 // to the slices of memory corresponding to each slab.
366 sort_by_field(chunks, [](const std::pair<Index_, Slab*>& x) -> Index_ { return x.first; });
367
368 serialize([&]() -> void {
369 auto& components = *my_h5comp;
370 auto& dspace = my_h5comp->dataspace;
371 dspace.selectNone();
372
373 // Remember, the slab size is equal to the product of the chunk length and the
374 // non-target length, so shifting the memory pool offsets by 'slab_size' will
375 // correspond to a shift of 'chunk_length' on the target dimension. The only
376 // exception is that of the last chunk, but at that point it doesn't matter as
377 // there's no data following the last chunk.
378 Index_ run_chunk_id = chunks.front().first;
379 Index_ chunk_length = tatami_chunked::get_chunk_length(my_dim_stats, run_chunk_id);
380 Index_ run_length = chunk_length;
381 Index_ total_length = chunk_length;
382 chunks.front().second->offset = running_offset;
383 auto start_offset = running_offset;
384 running_offset += my_slab_size;
385
386 for (size_t ci = 1, cend = chunks.size(); ci < cend; ++ci) {
387 auto& current_chunk = chunks[ci];
388 Index_ current_chunk_id = current_chunk.first;
389
390 if (current_chunk_id - run_chunk_id > 1) { // save the existing run of chunks as one hyperslab, and start a new run.
391 unionize(dspace, run_chunk_id * my_dim_stats.chunk_length, run_length);
392 run_chunk_id = current_chunk_id;
393 run_length = 0;
394 }
395
396 Index_ current_length = tatami_chunked::get_chunk_length(my_dim_stats, current_chunk_id);
397 run_length += current_length;
398 total_length += current_length;
399 current_chunk.second->offset = running_offset;
400 running_offset += my_slab_size;
401 }
402
403 unionize(dspace, run_chunk_id * my_dim_stats.chunk_length, run_length);
404
405 hsize_t count[2];
406 count[0] = total_length;
407 count[1] = non_target_length;
408 components.memspace.setExtentSimple(2, count);
409 components.memspace.selectAll();
410 components.dataset.read(dest + start_offset, define_mem_type<CachedValue_>(), components.memspace, dspace);
411 });
412 }
413 );
414
415 auto ptr = my_memory_pool.data() + info.first->offset + non_target_length * static_cast<size_t>(info.second); // cast to size_t to avoid overflow
416 std::copy_n(ptr, non_target_length, buffer);
417 }
418
419public:
420 template<typename Value_>
421 const Value_* fetch_block(Index_ i, Index_ block_start, Index_ block_length, Value_* buffer) {
422 fetch_raw(
423 i,
424 buffer,
425 block_length,
426 [&](H5::DataSpace& dspace, Index_ run_start, Index_ run_length) -> void {
427 hsize_t offset[2];
428 hsize_t count[2];
429 offset[0] = run_start;
430 offset[1] = block_start;
431 count[0] = run_length;
432 count[1] = block_length;
433 dspace.selectHyperslab(H5S_SELECT_OR, count, offset);
434 }
435 );
436 return buffer;
437 }
438
439 template<typename Value_>
440 const Value_* fetch_indices(Index_ i, const std::vector<Index_>& indices, Value_* buffer) {
441 fetch_raw(
442 i,
443 buffer,
444 indices.size(),
445 [&](H5::DataSpace& dspace, Index_ run_start, Index_ run_length) -> void {
446 hsize_t offset[2];
447 hsize_t count[2];
448 offset[0] = run_start;
449 count[0] = run_length;
450
451 // See comments in extract_indices().
452 tatami::process_consecutive_indices<Index_>(
453 indices.data(),
454 indices.size(),
455 [&](Index_ start, Index_ length) -> void {
456 offset[1] = start;
457 count[1] = length;
458 dspace.selectHyperslab(H5S_SELECT_OR, count, offset);
459 }
460 );
461 }
462 );
463 return buffer;
464 }
465};
466
467// This class performs oracular dense extraction when each target dimension element is NOT a row in the HDF5 matrix.
468// This requires an additional transposition step for each slab after its extraction from the HDF5 library.
469template<typename Index_, typename CachedValue_>
470class OracularCoreTransposed {
471public:
472 OracularCoreTransposed(
473 const std::string& file_name,
474 const std::string& dataset_name,
475 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
477 const tatami_chunked::SlabCacheStats& slab_stats) :
478 my_dim_stats(std::move(target_dim_stats)),
479 my_factory(slab_stats),
480 my_cache(std::move(oracle), slab_stats.max_slabs_in_cache),
481 my_transposition_buffer(slab_stats.slab_size_in_elements),
482 my_transposition_buffer_ptr(my_transposition_buffer.data())
483 {
484 initialize(file_name, dataset_name, my_h5comp);
485 my_cache_transpose_info.reserve(slab_stats.max_slabs_in_cache);
486 }
487
488 ~OracularCoreTransposed() {
489 destroy(my_h5comp);
490 }
491
492private:
493 std::unique_ptr<Components> my_h5comp;
494 tatami_chunked::ChunkDimensionStats<Index_> my_dim_stats;
495
496 tatami_chunked::DenseSlabFactory<CachedValue_> my_factory;
497 typedef typename decltype(my_factory)::Slab Slab;
498 tatami_chunked::OracularSlabCache<Index_, Index_, Slab> my_cache;
499
500 std::vector<CachedValue_> my_transposition_buffer;
501 CachedValue_* my_transposition_buffer_ptr;
502 std::vector<std::pair<Slab*, Index_> > my_cache_transpose_info;
503
504private:
505 template<typename Value_, class Extract_>
506 void fetch_raw([[maybe_unused]] Index_ i, Value_* buffer, size_t non_target_length, Extract_ extract) {
507 auto info = my_cache.next(
508 /* identify = */ [&](Index_ current) -> std::pair<Index_, Index_> {
509 return std::pair<Index_, Index_>(current / my_dim_stats.chunk_length, current % my_dim_stats.chunk_length);
510 },
511 /* create = */ [&]() -> Slab {
512 return my_factory.create();
513 },
514 /* populate = */ [&](std::vector<std::pair<Index_, Slab*> >& chunks) -> void {
515 my_cache_transpose_info.clear();
516
517 serialize([&]() -> void {
518 for (const auto& c : chunks) {
519 auto curdim = tatami_chunked::get_chunk_length(my_dim_stats, c.first);
520 extract(c.first * my_dim_stats.chunk_length, curdim, c.second->data);
521 my_cache_transpose_info.emplace_back(c.second, curdim);
522 }
523 });
524
525 // Applying transpositions to each cached buffers for easier
526 // retrieval. Done outside the serial section to unblock other threads.
527 if (non_target_length != 1) {
528 for (const auto& c : my_cache_transpose_info) {
529 if (c.second != 1) {
530 tatami::transpose(c.first->data, non_target_length, c.second, my_transposition_buffer_ptr);
531
532 // We actually swap the pointers here, so the slab
533 // pointers might not point to the factory pool after this!
534 // Shouldn't matter as long as neither of them leave this class.
535 std::swap(c.first->data, my_transposition_buffer_ptr);
536 }
537 }
538 }
539 }
540 );
541
542 auto ptr = info.first->data + non_target_length * static_cast<size_t>(info.second); // cast to size_t to avoid overflow
543 std::copy_n(ptr, non_target_length, buffer);
544 }
545
546public:
547 template<typename Value_>
548 const Value_* fetch_block(Index_ i, Index_ block_start, Index_ block_length, Value_* buffer) {
549 fetch_raw(
550 i,
551 buffer,
552 block_length,
553 [&](Index_ start, Index_ length, CachedValue_* buf) -> void {
554 extract_block(false, start, length, block_start, block_length, buf, *my_h5comp);
555 }
556 );
557 return buffer;
558 }
559
560 template<typename Value_>
561 const Value_* fetch_indices(Index_ i, const std::vector<Index_>& indices, Value_* buffer) {
562 fetch_raw(
563 i,
564 buffer,
565 indices.size(),
566 [&](Index_ start, Index_ length, CachedValue_* buf) -> void {
567 extract_indices(false, start, length, indices, buf, *my_h5comp);
568 }
569 );
570 return buffer;
571 }
572};
573
574// COMMENT: technically, for all oracular extractors, we could pretend that the chunk length on the target dimension is 1.
575// This would allow us to only extract the desired indices on each call, allowing for more efficient use of the cache.
576// (In the transposed case, we would also reduce the amount of transposition that we need to perform.)
577// The problem is that we would get harshly penalized for any chunk reuse outside of the current prediction cycle,
578// where the chunk would need to be read from disk again if the exact elements weren't already in the cache.
579// This access pattern might not be uncommon after applying a DelayedSubset with shuffled rows/columns.
580
581template<bool solo_, bool oracle_, bool by_h5_row_, typename Index_, typename CachedValue_>
582using DenseCore = typename std::conditional<solo_,
583 SoloCore<oracle_, by_h5_row_, Index_>,
584 typename std::conditional<!oracle_,
585 MyopicCore<by_h5_row_, Index_, CachedValue_>,
586 typename std::conditional<by_h5_row_,
587 OracularCoreNormal<Index_, CachedValue_>,
588 OracularCoreTransposed<Index_, CachedValue_>
589 >::type
590 >::type
591>::type;
592
593/***************************
594 *** Concrete subclasses ***
595 ***************************/
596
597template<bool solo_, bool oracle_, bool by_h5_row_, typename Value_, typename Index_, typename CachedValue_>
598class Full : public tatami::DenseExtractor<oracle_, Value_, Index_> {
599public:
600 Full(
601 const std::string& file_name,
602 const std::string& dataset_name,
603 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
605 Index_ non_target_dim,
606 const tatami_chunked::SlabCacheStats& slab_stats) :
607 my_core(
608 file_name,
609 dataset_name,
610 std::move(target_dim_stats),
611 std::move(oracle),
612 slab_stats
613 ),
614 my_non_target_dim(non_target_dim)
615 {}
616
617 const Value_* fetch(Index_ i, Value_* buffer) {
618 return my_core.fetch_block(i, 0, my_non_target_dim, buffer);
619 }
620
621private:
622 DenseCore<solo_, oracle_, by_h5_row_, Index_, CachedValue_> my_core;
623 Index_ my_non_target_dim;
624};
625
626template<bool solo_, bool oracle_, bool by_h5_row_, typename Value_, typename Index_, typename CachedValue_>
627class Block : public tatami::DenseExtractor<oracle_, Value_, Index_> {
628public:
629 Block(
630 const std::string& file_name,
631 const std::string& dataset_name,
632 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
634 Index_ block_start,
635 Index_ block_length,
636 const tatami_chunked::SlabCacheStats& slab_stats) :
637 my_core(
638 file_name,
639 dataset_name,
640 std::move(target_dim_stats),
641 std::move(oracle),
642 slab_stats
643 ),
644 my_block_start(block_start),
645 my_block_length(block_length)
646 {}
647
648 const Value_* fetch(Index_ i, Value_* buffer) {
649 return my_core.fetch_block(i, my_block_start, my_block_length, buffer);
650 }
651
652private:
653 DenseCore<solo_, oracle_, by_h5_row_, Index_, CachedValue_> my_core;
654 Index_ my_block_start, my_block_length;
655};
656
657template<bool solo_, bool oracle_, bool by_h5_row_, typename Value_, typename Index_, typename CachedValue_>
658class Index : public tatami::DenseExtractor<oracle_, Value_, Index_> {
659public:
660 Index(
661 const std::string& file_name,
662 const std::string& dataset_name,
663 tatami_chunked::ChunkDimensionStats<Index_> target_dim_stats,
665 tatami::VectorPtr<Index_> indices_ptr,
666 const tatami_chunked::SlabCacheStats& slab_stats) :
667 my_core(
668 file_name,
669 dataset_name,
670 std::move(target_dim_stats),
671 std::move(oracle),
672 slab_stats
673 ),
674 my_indices_ptr(std::move(indices_ptr))
675 {}
676
677 const Value_* fetch(Index_ i, Value_* buffer) {
678 return my_core.fetch_indices(i, *my_indices_ptr, buffer);
679 }
680
681private:
682 DenseCore<solo_, oracle_, by_h5_row_, Index_, CachedValue_> my_core;
683 tatami::VectorPtr<Index_> my_indices_ptr;
684};
685
686}
715template<typename Value_, typename Index_, typename CachedValue_ = Value_>
716class DenseMatrix : public tatami::Matrix<Value_, Index_> {
717 std::string my_file_name, my_dataset_name;
718 bool my_transpose;
719
720 size_t my_cache_size_in_elements;
721 bool my_require_minimum_cache;
722
723 tatami_chunked::ChunkDimensionStats<Index_> my_firstdim_stats, my_seconddim_stats;
724 bool my_prefer_firstdim;
725
726public:
735 DenseMatrix(std::string file, std::string name, bool transpose, const DenseMatrixOptions& options) :
736 my_file_name(std::move(file)),
737 my_dataset_name(std::move(name)),
738 my_transpose(transpose),
739 my_cache_size_in_elements(options.maximum_cache_size / sizeof(CachedValue_)),
740 my_require_minimum_cache(options.require_minimum_cache)
741 {
742 serialize([&]() -> void {
743 H5::H5File fhandle(my_file_name, H5F_ACC_RDONLY);
744 auto dhandle = open_and_check_dataset<false>(fhandle, my_dataset_name);
745 auto dims = get_array_dimensions<2>(dhandle, my_dataset_name);
746
747 hsize_t chunk_dims[2];
748 auto dparms = dhandle.getCreatePlist();
749 if (dparms.getLayout() != H5D_CHUNKED) {
750 // If contiguous, each firstdim is treated as a chunk.
751 chunk_dims[0] = 1;
752 chunk_dims[1] = dims[1];
753 } else {
754 dparms.getChunk(2, chunk_dims);
755 }
756
757 my_firstdim_stats = tatami_chunked::ChunkDimensionStats<Index_>(dims[0], chunk_dims[0]);
758 my_seconddim_stats = tatami_chunked::ChunkDimensionStats<Index_>(dims[1], chunk_dims[1]);
759 });
760
761 // Favoring extraction on the dimension that involves pulling out fewer
762 // chunks per dimension element. Remember, 'firstdim_stats.num_chunks'
763 // represents the number of chunks along the first dimension, and thus
764 // is the number of chunks that need to be loaded to pull out an
765 // element of the **second** dimension; and vice versa.
766 my_prefer_firstdim = (my_firstdim_stats.num_chunks > my_seconddim_stats.num_chunks);
767 }
768
775 DenseMatrix(std::string file, std::string name, bool transpose) :
776 DenseMatrix(std::move(file), std::move(name), transpose, DenseMatrixOptions()) {}
777
778private:
779 bool prefer_rows_internal() const {
780 if (my_transpose) {
781 return !my_prefer_firstdim;
782 } else {
783 return my_prefer_firstdim;
784 }
785 }
786
787 Index_ nrow_internal() const {
788 if (my_transpose) {
789 return my_seconddim_stats.dimension_extent;
790 } else {
791 return my_firstdim_stats.dimension_extent;
792 }
793 }
794
795 Index_ ncol_internal() const {
796 if (my_transpose) {
797 return my_firstdim_stats.dimension_extent;
798 } else {
799 return my_seconddim_stats.dimension_extent;
800 }
801 }
802
803public:
804 Index_ nrow() const {
805 return nrow_internal();
806 }
807
808 Index_ ncol() const {
809 return ncol_internal();
810 }
811
819 bool prefer_rows() const {
820 return prefer_rows_internal();
821 }
822
823 double prefer_rows_proportion() const {
824 return static_cast<double>(prefer_rows_internal());
825 }
826
827 bool uses_oracle(bool) const {
828 return true;
829 }
830
831 bool is_sparse() const {
832 return false;
833 }
834
835 double is_sparse_proportion() const {
836 return 0;
837 }
838
839 using tatami::Matrix<Value_, Index_>::dense;
840
841 using tatami::Matrix<Value_, Index_>::sparse;
842
843private:
844 template<bool oracle_, template<bool, bool, bool, typename, typename, typename> class Extractor_, typename ... Args_>
845 std::unique_ptr<tatami::DenseExtractor<oracle_, Value_, Index_> > populate(bool row, Index_ non_target_length, tatami::MaybeOracle<oracle_, Index_> oracle, Args_&& ... args) const {
846 bool by_h5_row = (row != my_transpose);
847 const auto& dim_stats = (by_h5_row ? my_firstdim_stats : my_seconddim_stats);
848
849 tatami_chunked::SlabCacheStats slab_stats(dim_stats.chunk_length, non_target_length, dim_stats.num_chunks, my_cache_size_in_elements, my_require_minimum_cache);
850 if (slab_stats.max_slabs_in_cache > 0) {
851 if (by_h5_row) {
852 return std::make_unique<Extractor_<false, oracle_, true, Value_, Index_, CachedValue_> >(
853 my_file_name, my_dataset_name, dim_stats, std::move(oracle), std::forward<Args_>(args)..., slab_stats
854 );
855 } else {
856 return std::make_unique<Extractor_<false, oracle_, false, Value_, Index_, CachedValue_> >(
857 my_file_name, my_dataset_name, dim_stats, std::move(oracle), std::forward<Args_>(args)..., slab_stats
858 );
859 }
860
861 } else {
862 if (by_h5_row) {
863 return std::make_unique<Extractor_<true, oracle_, true, Value_, Index_, CachedValue_> >(
864 my_file_name, my_dataset_name, dim_stats, std::move(oracle), std::forward<Args_>(args)..., slab_stats
865 );
866 } else {
867 return std::make_unique<Extractor_<true, oracle_, false, Value_, Index_, CachedValue_> >(
868 my_file_name, my_dataset_name, dim_stats, std::move(oracle), std::forward<Args_>(args)..., slab_stats
869 );
870 }
871 }
872 }
873
874 /********************
875 *** Myopic dense ***
876 ********************/
877public:
878 std::unique_ptr<tatami::MyopicDenseExtractor<Value_, Index_> > dense(bool row, const tatami::Options&) const {
879 Index_ full_non_target = (row ? ncol_internal() : nrow_internal());
880 return populate<false, DenseMatrix_internal::Full>(row, full_non_target, false, full_non_target);
881 }
882
883 std::unique_ptr<tatami::MyopicDenseExtractor<Value_, Index_> > dense(bool row, Index_ block_start, Index_ block_length, const tatami::Options&) const {
884 return populate<false, DenseMatrix_internal::Block>(row, block_length, false, block_start, block_length);
885 }
886
887 std::unique_ptr<tatami::MyopicDenseExtractor<Value_, Index_> > dense(bool row, tatami::VectorPtr<Index_> indices_ptr, const tatami::Options&) const {
888 auto nidx = indices_ptr->size();
889 return populate<false, DenseMatrix_internal::Index>(row, nidx, false, std::move(indices_ptr));
890 }
891
892 /*********************
893 *** Myopic sparse ***
894 *********************/
895public:
896 std::unique_ptr<tatami::MyopicSparseExtractor<Value_, Index_> > sparse(bool row, const tatami::Options& opt) const {
897 Index_ full_non_target = (row ? ncol_internal() : nrow_internal());
898 return std::make_unique<tatami::FullSparsifiedWrapper<false, Value_, Index_> >(dense(row, opt), full_non_target, opt);
899 }
900
901 std::unique_ptr<tatami::MyopicSparseExtractor<Value_, Index_> > sparse(bool row, Index_ block_start, Index_ block_length, const tatami::Options& opt) const {
902 return std::make_unique<tatami::BlockSparsifiedWrapper<false, Value_, Index_> >(dense(row, block_start, block_length, opt), block_start, block_length, opt);
903 }
904
905 std::unique_ptr<tatami::MyopicSparseExtractor<Value_, Index_> > sparse(bool row, tatami::VectorPtr<Index_> indices_ptr, const tatami::Options& opt) const {
906 auto ptr = dense(row, indices_ptr, opt);
907 return std::make_unique<tatami::IndexSparsifiedWrapper<false, Value_, Index_> >(std::move(ptr), std::move(indices_ptr), opt);
908 }
909
910 /**********************
911 *** Oracular dense ***
912 **********************/
913public:
914 std::unique_ptr<tatami::OracularDenseExtractor<Value_, Index_> > dense(
915 bool row,
916 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
917 const tatami::Options&)
918 const {
919 Index_ full_non_target = (row ? ncol_internal() : nrow_internal());
920 return populate<true, DenseMatrix_internal::Full>(row, full_non_target, std::move(oracle), full_non_target);
921 }
922
923 std::unique_ptr<tatami::OracularDenseExtractor<Value_, Index_> > dense(
924 bool row,
925 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
926 Index_ block_start,
927 Index_ block_length,
928 const tatami::Options&)
929 const {
930 return populate<true, DenseMatrix_internal::Block>(row, block_length, std::move(oracle), block_start, block_length);
931 }
932
933 std::unique_ptr<tatami::OracularDenseExtractor<Value_, Index_> > dense(
934 bool row,
935 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
936 tatami::VectorPtr<Index_> indices_ptr,
937 const tatami::Options&)
938 const {
939 auto nidx = indices_ptr->size();
940 return populate<true, DenseMatrix_internal::Index>(row, nidx, std::move(oracle), std::move(indices_ptr));
941 }
942
943 /***********************
944 *** Oracular sparse ***
945 ***********************/
946public:
947 std::unique_ptr<tatami::OracularSparseExtractor<Value_, Index_> > sparse(
948 bool row,
949 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
950 const tatami::Options& opt)
951 const {
952 Index_ full_non_target = (row ? ncol_internal() : nrow_internal());
953 return std::make_unique<tatami::FullSparsifiedWrapper<true, Value_, Index_> >(dense(row, std::move(oracle), opt), full_non_target, opt);
954 }
955
956 std::unique_ptr<tatami::OracularSparseExtractor<Value_, Index_> > sparse(
957 bool row,
958 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
959 Index_ block_start,
960 Index_ block_length,
961 const tatami::Options& opt)
962 const {
963 return std::make_unique<tatami::BlockSparsifiedWrapper<true, Value_, Index_> >(dense(row, std::move(oracle), block_start, block_length, opt), block_start, block_length, opt);
964 }
965
966 std::unique_ptr<tatami::OracularSparseExtractor<Value_, Index_> > sparse(
967 bool row,
968 std::shared_ptr<const tatami::Oracle<Index_> > oracle,
969 tatami::VectorPtr<Index_> indices_ptr,
970 const tatami::Options& opt)
971 const {
972 auto ptr = dense(row, std::move(oracle), indices_ptr, opt);
973 return std::make_unique<tatami::IndexSparsifiedWrapper<true, Value_, Index_> >(std::move(ptr), std::move(indices_ptr), opt);
974 }
975};
976
977}
978
979#endif
Dense matrix backed by a DataSet in a HDF5 file.
Definition DenseMatrix.hpp:716
DenseMatrix(std::string file, std::string name, bool transpose)
Definition DenseMatrix.hpp:775
bool prefer_rows() const
Definition DenseMatrix.hpp:819
DenseMatrix(std::string file, std::string name, bool transpose, const DenseMatrixOptions &options)
Definition DenseMatrix.hpp:735
Representations for matrix data in HDF5 files.
Definition CompressedSparseMatrix.hpp:23
void serialize(Function_ f)
Definition serialize.hpp:45
void transpose(const Input_ *input, size_t nrow, size_t ncol, size_t input_stride, Output_ *output, size_t output_stride)
std::shared_ptr< const std::vector< Index_ > > VectorPtr
typename std::conditional< oracle_, std::shared_ptr< const Oracle< Index_ > >, bool >::type MaybeOracle
typename std::conditional< oracle_, OracularDenseExtractor< Value_, Index_ >, MyopicDenseExtractor< Value_, Index_ > >::type DenseExtractor
void process_consecutive_indices(const Index_ *indices, Index_ length, Function_ fun)
Default locking for serial access.
Options for DenseMatrix extraction.
Definition DenseMatrix.hpp:26
bool require_minimum_cache
Definition DenseMatrix.hpp:43
size_t maximum_cache_size
Definition DenseMatrix.hpp:36
Utilities for HDF5 extraction.