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