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