tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
range.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_RANGE_HPP
2#define TATAMI_STATS_RANGE_HPP
3
4#include "utils.hpp"
5
6#include <vector>
7#include <algorithm>
8#include <type_traits>
9
10#include "tatami/tatami.hpp"
11
18namespace tatami_stats {
19
28 bool skip_nan = false;
29
34 int num_threads = 1;
35};
36
40template<typename Value_>
41constexpr Value_ choose_minimum_placeholder() {
42 // Placeholder value 'x' is such that 'x >= y' is always true for any non-NaN 'y'.
43 if constexpr(std::numeric_limits<Value_>::has_infinity) {
44 return std::numeric_limits<Value_>::infinity();
45 } else {
46 return std::numeric_limits<Value_>::max();
47 }
48}
49
50template<typename Value_>
51constexpr Value_ choose_maximum_placeholder() {
52 // Placeholder value 'x' is such that 'x <= y' is always true for any non-NaN 'y'.
53 if constexpr(std::numeric_limits<Value_>::has_infinity) {
54 return -std::numeric_limits<Value_>::infinity();
55 } else {
56 return std::numeric_limits<Value_>::lowest();
57 }
58}
59
60template<typename Value_, typename Index_>
61Value_ min_direct(const Value_* const ptr, const Index_ num, bool skip_nan) {
62 return nanable_ifelse_with_value<Value_>(
63 skip_nan,
64 [&]() -> Value_ {
65 auto current = choose_minimum_placeholder<Value_>();
66 for (Index_ i = 0; i < num; ++i) {
67 auto val = ptr[i];
68 if (val < current) { // no need to explicitly handle NaNs, as any comparison with NaNs is always false.
69 current = val;
70 }
71 }
72 return current;
73 },
74 [&]() -> Value_ {
75 if (num) {
76 return *std::min_element(ptr, ptr + num);
77 } else {
78 return choose_minimum_placeholder<Value_>();
79 }
80 }
81 );
82}
83
84template<typename Value_, typename Index_>
85Value_ max_direct(const Value_* ptr, const Index_ num, bool skip_nan) {
86 return nanable_ifelse_with_value<Value_>(
87 skip_nan,
88 [&]() -> Value_ {
89 auto current = choose_maximum_placeholder<Value_>();
90 for (Index_ i = 0; i < num; ++i) {
91 auto val = ptr[i];
92 if (val > current) { // again, no need to explicitly handle NaNs, as any comparison with NaNs is always false.
93 current = val;
94 }
95 }
96 return current;
97 },
98 [&]() -> Value_ {
99 if (num) {
100 return *std::max_element(ptr, ptr + num);
101 } else {
102 return choose_maximum_placeholder<Value_>();
103 }
104 }
105 );
106}
107
108template<typename Value_, typename Index_>
109Value_ min_direct(const Value_* value, const Index_ num_nonzero, const Index_ num_all, bool skip_nan) {
110 if (num_nonzero) {
111 auto candidate = min_direct(value, num_nonzero, skip_nan);
112 if (num_nonzero < num_all) {
113 if (candidate > 0) {
114 candidate = 0;
115 }
116 }
117 return candidate;
118 } else if (num_all) {
119 return 0;
120 } else {
121 return choose_minimum_placeholder<Value_>();
122 }
123}
124
125template<typename Value_, typename Index_>
126Value_ max_direct(const Value_* value, const Index_ num_nonzero, const Index_ num_all, bool skip_nan) {
127 if (num_nonzero) {
128 auto candidate = max_direct(value, num_nonzero, skip_nan);
129 if (num_nonzero < num_all) {
130 if (candidate < 0) {
131 candidate = 0;
132 }
133 }
134 return candidate;
135 } else if (num_all) {
136 return 0;
137 } else {
138 return choose_maximum_placeholder<Value_>();
139 }
140}
151template<typename Output_>
157 Output_* minimum;
158
163 Output_* maximum;
164};
165
169template<typename Value_, typename Index_, typename Output_>
170void range_direct(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_>& output, const RangeOptions& opt) {
171 const auto dim = (row ? mat.nrow() : mat.ncol());
172 const auto otherdim = (row ? mat.ncol() : mat.nrow());
173
174 if (mat.is_sparse()) {
175 tatami::Options topt;
176 topt.sparse_extract_index = false;
177 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
178 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l, topt);
180 for (Index_ x = 0; x < l; ++x) {
181 auto out = ext->fetch(vbuffer.data(), NULL);
182 output.minimum[x + s] = min_direct(out.value, out.number, otherdim, opt.skip_nan);
183 output.maximum[x + s] = max_direct(out.value, out.number, otherdim, opt.skip_nan);
184 }
185 }, dim, opt.num_threads);
186
187 } else {
188 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
189 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
191 for (Index_ x = 0; x < l; ++x) {
192 auto ptr = ext->fetch(buffer.data());
193 output.minimum[x + s] = min_direct(ptr, otherdim, opt.skip_nan);
194 output.maximum[x + s] = max_direct(ptr, otherdim, opt.skip_nan);
195 }
196 }, dim, opt.num_threads);
197 }
198}
199
200template<typename Value_, typename Index_, typename Output_>
201void range_running(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_>& output, const RangeOptions& opt) {
202 const auto dim = (row ? mat.nrow() : mat.ncol());
203 const auto otherdim = (row ? mat.ncol() : mat.nrow());
204 const bool is_sparse = mat.is_sparse();
205
206 const bool do_parallel = opt.num_threads > 1;
207 std::optional<std::vector<std::optional<std::vector<Output_> > > > all_partial_min, all_partial_max;
208 if (do_parallel) {
209 all_partial_min.emplace(sanisizer::cast<I<decltype(all_partial_min->size())> >(opt.num_threads - 1));
210 all_partial_max.emplace(sanisizer::cast<I<decltype(all_partial_max->size())> >(opt.num_threads - 1));
211 }
212
213 constexpr auto min_placeholder = choose_minimum_placeholder<Value_>();
214 constexpr auto max_placeholder = choose_maximum_placeholder<Value_>();
215 if (opt.skip_nan || otherdim == 0) {
216 // If we're not skipping NaNs and we have at least one dimension element,
217 // the output arrays will be fully populated when thread 0 processes the first dimension element.
218 std::fill_n(output.minimum, dim, min_placeholder);
219 std::fill_n(output.maximum, dim, max_placeholder);
220 }
221
222 const auto nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
223 Output_* min_ptr;
224 Output_* max_ptr;
225 std::optional<std::vector<Output_> > cur_min, cur_max;
226 if (!do_parallel) {
227 min_ptr = output.minimum;
228 max_ptr = output.maximum;
229 } else {
230 if (thread == 0) {
231 min_ptr = output.minimum;
232 max_ptr = output.maximum;
233 } else {
234 cur_min.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
235 cur_max.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
236 min_ptr = cur_min->data();
237 max_ptr = cur_max->data();
238 }
239 }
240
241 if (is_sparse) {
242 tatami::Options topt;
243 topt.sparse_ordered_index = false;
244 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l, topt);
248
249 for (Index_ x = 0; x < l; ++x) {
250 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
251
252 if (x == 0) {
253 // For the first observed vector in each thread,
254 // we can optimize it a little as we don't need to read existing min/max.
255 nanable_ifelse<Value_>(
256 opt.skip_nan,
257 [&]() -> void {
258 for (Index_ i = 0; i < out.number; ++i) {
259 const auto val = out.value[i];
260 if (!std::isnan(val)) {
261 const auto idx = out.index[i];
262 min_ptr[idx] = val;
263 max_ptr[idx] = val;
264 ++nonzeros[idx];
265 }
266 }
267 },
268 [&]() -> void {
269 // For non-main threads, our thread-local buffer is already zeroed so no need to do this.
270 if (!do_parallel || thread == 0) {
271 std::fill_n(min_ptr, dim, 0);
272 std::fill_n(max_ptr, dim, 0);
273 }
274 for (Index_ i = 0; i < out.number; ++i) {
275 const auto val = out.value[i];
276 const auto idx = out.index[i];
277 min_ptr[idx] = val;
278 max_ptr[idx] = val;
279 ++nonzeros[idx];
280 }
281 }
282 );
283
284 } else {
285 for (Index_ i = 0; i < out.number; ++i) {
286 const auto val = out.value[i];
287 const auto idx = out.index[i];
288 auto& min_current = min_ptr[idx];
289 if (val < min_current) { // this should implicitly skip val=NaNs, as any NaN comparison will be false.
290 min_current = val;
291 }
292 auto& max_current = max_ptr[idx];
293 if (val > max_current) { // this should implicitly skip val=NaNs, as any NaN comparison will be false.
294 max_current = val;
295 }
296 ++nonzeros[idx];
297 }
298 }
299 }
300
301 for (Index_ d = 0; d < dim; ++d) {
302 if (l > nonzeros[d]) {
303 auto& min_current = min_ptr[d];
304 if (min_current > 0) {
305 min_current = 0;
306 }
307 auto& max_current = max_ptr[d];
308 if (max_current < 0) {
309 max_current = 0;
310 }
311 }
312 }
313
314 } else {
315 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
317
318 for (Index_ x = 0; x < l; ++x) {
319 auto ptr = ext->fetch(buffer.data());
320
321 if (x == 0) {
322 // For the first observed vector in each thread,
323 // we can optimize it a little as we don't need to read existing min/max.
324 nanable_ifelse<Value_>(
325 opt.skip_nan,
326 [&]() -> void {
327 for (Index_ i = 0; i < dim; ++i) {
328 const auto val = ptr[i];
329 if (!std::isnan(val)) {
330 min_ptr[i] = val;
331 max_ptr[i] = val;
332 }
333 }
334 },
335 [&]() -> void {
336 std::copy_n(ptr, dim, min_ptr);
337 std::copy_n(ptr, dim, max_ptr);
338 }
339 );
340
341 } else {
342 for (Index_ i = 0; i < dim; ++i) {
343 const auto val = ptr[i];
344 auto& min_current = min_ptr[i];
345 if (val < min_current) { // this should implicitly skip val=NaNs, as any NaN comparison will be false.
346 min_current = val;
347 }
348 auto& max_current = max_ptr[i];
349 if (val > max_current) { // this should implicitly skip val=NaNs, as any NaN comparison will be false.
350 max_current = val;
351 }
352 }
353 }
354 }
355 }
356
357 if (do_parallel) {
358 if (thread > 0) {
359 (*all_partial_min)[thread - 1] = std::move(cur_min);
360 (*all_partial_max)[thread - 1] = std::move(cur_max);
361 }
362 }
363 }, otherdim, opt.num_threads);
364
365 if (do_parallel) {
366 for (int u = 1; u < nused; ++u) {
367 const auto& cur_min = *((*all_partial_min)[u - 1]);
368 const auto& cur_max = *((*all_partial_max)[u - 1]);
369 for (Index_ d = 0; d < dim; ++d) {
370 if (output.minimum[d] > cur_min[d]) {
371 output.minimum[d] = cur_min[d];
372 }
373 if (output.maximum[d] < cur_max[d]) {
374 output.maximum[d] = cur_max[d];
375 }
376 }
377 }
378 }
379}
399template<typename Value_, typename Index_, typename Output_>
400void range(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_>& output, const RangeOptions& opt) {
401 if (mat.prefer_rows() == row) {
402 range_direct(row, mat, output, opt);
403 } else {
404 range_running(row, mat, output, opt);
405 }
406}
407
414template<typename Output_>
420 std::vector<Output_> minimum;
421
426 std::vector<Output_> maximum;
427};
428
444template<typename Value_, typename Index_, typename Output_ = Value_>
447 const auto dim = (row ? mat.nrow() : mat.ncol());
449#ifdef TATAMI_STATS_TEST_DIRTY
450 , -1
451#endif
452 );
454#ifdef TATAMI_STATS_TEST_DIRTY
455 , -1
456#endif
457 );
458
459 RangeBuffers<Output_> buffers;
460 buffers.minimum = output.minimum.data();
461 buffers.maximum = output.maximum.data();
462 range(row, mat, buffers, opt);
463
464 return output;
465}
466
467}
468
469#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
Functions to compute statistics from a tatami::Matrix.
Definition count.hpp:20
void range(bool row, const tatami::Matrix< Value_, Index_ > &mat, RangeBuffers< Output_ > &output, const RangeOptions &opt)
Definition range.hpp:400
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
Result buffers for range().
Definition range.hpp:152
Output_ * maximum
Definition range.hpp:163
Output_ * minimum
Definition range.hpp:157
Options for range().
Definition range.hpp:23
bool skip_nan
Definition range.hpp:28
int num_threads
Definition range.hpp:34
Results of range().
Definition range.hpp:415
std::vector< Output_ > maximum
Definition range.hpp:426
std::vector< Output_ > minimum
Definition range.hpp:420