tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
range.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_SKIP_NAN_RANGE_HPP
2#define TATAMI_STATS_SKIP_NAN_RANGE_HPP
3
4#include "../utils.hpp"
5
6#include <vector>
7#include <algorithm>
8#include <type_traits>
9#include <limits>
10
11#include "tatami/tatami.hpp"
12
19namespace tatami_stats {
20
21namespace skip_nan {
22
28template<typename Output_>
29constexpr Output_ default_minimum_placeholder() {
30 if constexpr(std::numeric_limits<Output_>::has_infinity) {
31 return std::numeric_limits<Output_>::infinity();
32 } else {
33 return std::numeric_limits<Output_>::max();
34 }
35}
36
42template<typename Output_>
43constexpr Output_ default_maximum_placeholder() {
44 if constexpr(std::numeric_limits<Output_>::has_infinity) {
45 return -std::numeric_limits<Output_>::infinity();
46 } else {
47 return std::numeric_limits<Output_>::lowest();
48 }
49}
50
55template<typename Output_ = double>
75
79template<typename Output_, typename Index_>
80struct RangeDirectResult {
81 Output_ minimum;
82 Output_ maximum;
83 Index_ count;
84};
85
86template<typename Value_, typename Index_, typename Output_>
87RangeDirectResult<Output_, Index_> range_direct(const Value_* const ptr, const Index_ num, const RangeOptions<Output_>& opt) {
88 RangeDirectResult<Output_, Index_> output;
89 output.minimum = opt.minimum_placeholder;
90 output.maximum = opt.maximum_placeholder;
91 output.count = 0;
92
93 // First loop to get to the first non-NA value.
94 Index_ i = 0;
95 for (; i < num; ++i) {
96 auto val = ptr[i];
97 if (!std::isnan(val)) {
98 output.minimum = val;
99 output.maximum = val;
100 output.count = 1;
101 ++i;
102 break;
103 }
104 }
105
106 // Second loop to actually get the minimum.
107 for (; i < num; ++i) {
108 auto val = ptr[i];
109 if (!std::isnan(val)) {
110 ++output.count;
111 output.minimum = std::min(output.minimum, val);
112 output.maximum = std::max(output.maximum, val);
113 }
114 }
115
116 return output;
117}
118
119template<typename Value_, typename Index_, typename Output_>
120RangeDirectResult<Output_, Index_> range_direct(const Value_* value, const Index_ num_nonzero, const Index_ num_all, const RangeOptions<Output_>& opt) {
121 if (num_nonzero) {
122 auto candidate = range_direct(value, num_nonzero, opt);
123 if (num_nonzero < num_all) {
124 candidate.minimum = std::min(candidate.minimum, static_cast<Output_>(0));
125 candidate.maximum = std::max(candidate.maximum, static_cast<Output_>(0));
126 candidate.count += num_all - num_nonzero;
127 }
128 return candidate;
129 } else if (num_all) {
130 RangeDirectResult<Output_, Index_> output;
131 output.minimum = 0;
132 output.maximum = 0;
133 output.count = num_all;
134 return output;
135 } else {
136 RangeDirectResult<Output_, Index_> output;
137 output.minimum = opt.minimum_placeholder;
138 output.maximum = opt.maximum_placeholder;
139 output.count = 0;
140 return output;
141 }
142}
154template<typename Output_, typename Count_>
160 Output_* minimum;
161
166 Output_* maximum;
167
172 Count_* count;
173};
174
178template<typename Value_, typename Index_, typename Output_, typename Count_>
179void range_direct(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_, Count_>& output, const RangeOptions<Output_>& opt) {
180 const auto dim = (row ? mat.nrow() : mat.ncol());
181 const auto otherdim = (row ? mat.ncol() : mat.nrow());
182
183 if (mat.is_sparse()) {
184 tatami::Options topt;
185 topt.sparse_extract_index = false;
186 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
187 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l, topt);
189 for (Index_ x = 0; x < l; ++x) {
190 auto out = ext->fetch(vbuffer.data(), NULL);
191 auto res = range_direct(out.value, out.number, otherdim, opt);
192 output.minimum[x + s] = res.minimum;
193 output.maximum[x + s] = res.maximum;
194 output.count[x + s] = res.count;
195 }
196 }, dim, opt.num_threads);
197
198 } else {
199 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
200 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
202 for (Index_ x = 0; x < l; ++x) {
203 auto ptr = ext->fetch(buffer.data());
204 auto res = range_direct(ptr, otherdim, opt);
205 output.minimum[x + s] = res.minimum;
206 output.maximum[x + s] = res.maximum;
207 output.count[x + s] = res.count;
208 }
209 }, dim, opt.num_threads);
210 }
211}
212
213template<typename Value_, typename Index_, typename Output_, typename Count_>
214void range_running(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_, Count_>& output, const RangeOptions<Output_>& opt) {
215 const auto dim = (row ? mat.nrow() : mat.ncol());
216 const auto otherdim = (row ? mat.ncol() : mat.nrow());
217 const bool is_sparse = mat.is_sparse();
218
219 const bool do_parallel = opt.num_threads > 1;
220 std::optional<std::vector<std::optional<std::vector<Output_> > > > all_partial_min, all_partial_max;
221 std::optional<std::vector<std::optional<std::vector<Count_> > > > all_partial_count;
222 if (do_parallel) {
223 all_partial_min.emplace(sanisizer::cast<I<decltype(all_partial_min->size())> >(opt.num_threads - 1));
224 all_partial_max.emplace(sanisizer::cast<I<decltype(all_partial_max->size())> >(opt.num_threads - 1));
225 all_partial_count.emplace(sanisizer::cast<I<decltype(all_partial_count->size())> >(opt.num_threads - 1));
226 }
227
228 std::fill_n(output.count, dim, 0);
229
230 // If we're not skipping NaNs and we have at least one dimension element,
231 // the output arrays will be fully populated when thread 0 processes the first dimension element.
232 if (otherdim == 0) {
233 std::fill_n(output.minimum, dim, opt.minimum_placeholder);
234 std::fill_n(output.maximum, dim, opt.maximum_placeholder);
235 return;
236 }
237
238 // No need to wipe dirty output buffers in the dense case, as we already set each entry of the output buffers.
239 if (is_sparse) {
240 std::fill_n(output.minimum, dim, 0);
241 std::fill_n(output.maximum, dim, 0);
242 }
243
244 const auto nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
245 Output_* min_ptr;
246 Output_* max_ptr;
247 Count_* count_ptr;
248 std::optional<std::vector<Output_> > cur_min, cur_max;
249 std::optional<std::vector<Count_> > cur_count;
250 if (!do_parallel) {
251 min_ptr = output.minimum;
252 max_ptr = output.maximum;
253 count_ptr = output.count;
254 } else {
255 if (thread == 0) {
256 min_ptr = output.minimum;
257 max_ptr = output.maximum;
258 count_ptr = output.count;
259 } else {
260 cur_min.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
261 cur_max.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
262 cur_count.emplace(tatami::cast_Index_to_container_size<std::vector<Count_> >(dim));
263 min_ptr = cur_min->data();
264 max_ptr = cur_max->data();
265 count_ptr = cur_count->data();
266 }
267 }
268
269 if (is_sparse) {
270 tatami::Options topt;
271 topt.sparse_ordered_index = false;
272 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l, topt);
276
277 for (Index_ x = 0; x < l; ++x) {
278 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
279
280 // For the first observed vector in each thread, we can optimize it a little as we don't need to read existing min/max.
281 if (x == 0) {
282 for (Index_ i = 0; i < out.number; ++i) {
283 const auto val = out.value[i];
284 const auto idx = out.index[i];
285 if (!std::isnan(val)) {
286 min_ptr[idx] = val;
287 max_ptr[idx] = val;
288 ++count_ptr[idx];
289 } else {
290 min_ptr[idx] = opt.minimum_placeholder;
291 max_ptr[idx] = opt.maximum_placeholder;
292 }
293 ++nonzeros[idx];
294 }
295 } else {
296 for (Index_ i = 0; i < out.number; ++i) {
297 const auto val = out.value[i];
298 const auto idx = out.index[i];
299 if (!std::isnan(val)) {
300 auto& min_current = min_ptr[idx];
301 auto& max_current = max_ptr[idx];
302 if (count_ptr[idx] == 0) {
303 min_current = val;
304 max_current = val;
305 } else {
306 min_current = std::min(min_current, val);
307 max_current = std::max(max_current, val);
308 }
309 ++count_ptr[idx];
310 }
311 ++nonzeros[idx];
312 }
313 }
314 }
315
316 for (Index_ d = 0; d < dim; ++d) {
317 if (l > nonzeros[d]) {
318 count_ptr[d] += l - nonzeros[d];
319 auto& min_current = min_ptr[d];
320 min_current = std::min(min_current, static_cast<Output_>(0));
321 auto& max_current = max_ptr[d];
322 max_current = std::max(max_current, static_cast<Output_>(0));
323 }
324 }
325
326 } else {
327 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
329
330 for (Index_ x = 0; x < l; ++x) {
331 auto ptr = ext->fetch(buffer.data());
332
333 if (x == 0) {
334 // For the first observed vector in each thread,
335 // we can optimize it a little as we don't need to read existing min/max.
336 for (Index_ i = 0; i < dim; ++i) {
337 const auto val = ptr[i];
338 if (!std::isnan(val)) {
339 min_ptr[i] = val;
340 max_ptr[i] = val;
341 ++count_ptr[i];
342 } else {
343 min_ptr[i] = opt.minimum_placeholder;
344 max_ptr[i] = opt.maximum_placeholder;
345 }
346 }
347 } else {
348 for (Index_ i = 0; i < dim; ++i) {
349 const auto val = ptr[i];
350 if (!std::isnan(val)) {
351 auto& min_current = min_ptr[i];
352 auto& max_current = max_ptr[i];
353 if (count_ptr[i] == 0) {
354 min_current = val;
355 max_current = val;
356 } else {
357 min_current = std::min(min_current, val);
358 max_current = std::max(max_current, val);
359 }
360 ++count_ptr[i];
361 }
362 }
363 }
364 }
365 }
366
367 if (do_parallel) {
368 if (thread > 0) {
369 (*all_partial_min)[thread - 1] = std::move(cur_min);
370 (*all_partial_max)[thread - 1] = std::move(cur_max);
371 (*all_partial_count)[thread - 1] = std::move(cur_count);
372 }
373 }
374 }, otherdim, opt.num_threads);
375
376 if (do_parallel) {
377 for (int u = 1; u < nused; ++u) {
378 const auto& cur_min = *((*all_partial_min)[u - 1]);
379 const auto& cur_max = *((*all_partial_max)[u - 1]);
380 const auto& cur_count = *((*all_partial_count)[u - 1]);
381 for (Index_ d = 0; d < dim; ++d) {
382 if (!cur_count[d]) {
383 continue;
384 }
385 if (output.count[d]) {
386 output.minimum[d] = std::min(cur_min[d], output.minimum[d]);
387 output.maximum[d] = std::max(cur_max[d], output.maximum[d]);
388 } else {
389 output.minimum[d] = cur_min[d];
390 output.maximum[d] = cur_max[d];
391 }
392 output.count[d] += cur_count[d];
393 }
394 }
395 }
396}
418template<typename Value_, typename Index_, typename Output_, typename Count_>
420 if (mat.prefer_rows() == row) {
421 range_direct(row, mat, output, opt);
422 } else {
423 range_running(row, mat, output, opt);
424 }
425}
426
434template<typename Output_, typename Count_>
440 std::vector<Output_> minimum;
441
446 std::vector<Output_> maximum;
447
452 std::vector<Count_> count;
453};
454
470template<typename Value_, typename Index_, typename Output_ = Value_, typename Count_ = Index_>
473 const auto dim = (row ? mat.nrow() : mat.ncol());
475#ifdef TATAMI_STATS_TEST_DIRTY
476 , -1
477#endif
478 );
480#ifdef TATAMI_STATS_TEST_DIRTY
481 , -1
482#endif
483 );
485#ifdef TATAMI_STATS_TEST_DIRTY
486 , -1
487#endif
488 );
489
491 buffers.minimum = output.minimum.data();
492 buffers.maximum = output.maximum.data();
493 buffers.count = output.count.data();
494 range(row, mat, buffers, opt);
495
496 return output;
497}
498
499}
500
501}
502
503#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
constexpr Output_ default_maximum_placeholder()
Definition range.hpp:43
void range(bool row, const tatami::Matrix< Value_, Index_ > &mat, RangeBuffers< Output_, Count_ > &output, const RangeOptions< Output_ > &opt)
Definition range.hpp:419
constexpr Output_ default_minimum_placeholder()
Definition range.hpp:29
Functions to compute statistics from a tatami::Matrix.
Definition count.hpp:20
void count(const bool row, const tatami::Matrix< Value_, Index_ > &mat, Output_ *const output, Condition_ condition, const CountOptions &opt)
Definition count.hpp:188
void resize_container_to_Index_size(Container_ &container, const Index_ x, Args_ &&... args)
int parallelize(Function_ fun, const Index_ tasks, const int workers)
I< decltype(std::declval< Container_ >().size())> cast_Index_to_container_size(const Index_ x)
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, const Index_ iter_start, const Index_ iter_length, Args_ &&... args)
bool sparse_extract_index
bool sparse_ordered_index
Options for range().
Definition range.hpp:54
Output_ minimum_placeholder
Definition range.hpp:65
Output_ maximum_placeholder
Definition range.hpp:71
Result buffers for skip_nan::range().
Definition range.hpp:155
Output_ * minimum
Definition range.hpp:160
Count_ * count
Definition range.hpp:172
Output_ * maximum
Definition range.hpp:166
Options for range().
Definition range.hpp:56
Output_ minimum_placeholder
Definition range.hpp:67
int num_threads
Definition range.hpp:61
Output_ maximum_placeholder
Definition range.hpp:73
Results of skip_nan::range().
Definition range.hpp:435
std::vector< Output_ > minimum
Definition range.hpp:440
std::vector< Output_ > maximum
Definition range.hpp:446
std::vector< Count_ > count
Definition range.hpp:452