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#include <limits>
10
11#include "tatami/tatami.hpp"
12
19namespace tatami_stats {
20
26template<typename Output_>
27constexpr Output_ default_minimum_placeholder() {
28 if constexpr(std::numeric_limits<Output_>::has_infinity) {
29 return std::numeric_limits<Output_>::infinity();
30 } else {
31 return std::numeric_limits<Output_>::max();
32 }
33}
34
40template<typename Output_>
41constexpr Output_ default_maximum_placeholder() {
42 if constexpr(std::numeric_limits<Output_>::has_infinity) {
43 return -std::numeric_limits<Output_>::infinity();
44 } else {
45 return std::numeric_limits<Output_>::lowest();
46 }
47}
48
53template<typename Output_ = double>
73
77template<typename Value_, typename Index_, typename Output_>
78Output_ min_direct(const Value_* const ptr, const Index_ num, const RangeOptions<Output_>& opt) {
79 if (num) {
80 return *std::min_element(ptr, ptr + num);
81 } else {
82 return opt.minimum_placeholder;
83 }
84}
85
86template<typename Value_, typename Index_, typename Output_>
87Output_ max_direct(const Value_* ptr, const Index_ num, const RangeOptions<Output_>& opt) {
88 if (num) {
89 return *std::max_element(ptr, ptr + num);
90 } else {
91 return opt.maximum_placeholder;
92 }
93}
94
95template<typename Value_, typename Index_, typename Output_>
96Output_ min_direct(const Value_* value, const Index_ num_nonzero, const Index_ num_all, const RangeOptions<Output_>& opt) {
97 if (num_nonzero) {
98 auto candidate = min_direct(value, num_nonzero, opt);
99 if (num_nonzero < num_all) {
100 if (candidate > 0) {
101 candidate = 0;
102 }
103 }
104 return candidate;
105 } else if (num_all) {
106 return 0;
107 } else {
108 return opt.minimum_placeholder;
109 }
110}
111
112template<typename Value_, typename Index_, typename Output_>
113Output_ max_direct(const Value_* value, const Index_ num_nonzero, const Index_ num_all, const RangeOptions<Output_>& opt) {
114 if (num_nonzero) {
115 auto candidate = max_direct(value, num_nonzero, opt);
116 if (num_nonzero < num_all) {
117 if (candidate < 0) {
118 candidate = 0;
119 }
120 }
121 return candidate;
122 } else if (num_all) {
123 return 0;
124 } else {
125 return opt.maximum_placeholder;
126 }
127}
137template<typename Output_>
143 Output_* minimum;
144
149 Output_* maximum;
150};
151
155template<typename Value_, typename Index_, typename Output_>
156void range_direct(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_>& output, const RangeOptions<Output_>& opt) {
157 const auto dim = (row ? mat.nrow() : mat.ncol());
158 const auto otherdim = (row ? mat.ncol() : mat.nrow());
159
160 if (mat.is_sparse()) {
161 tatami::Options topt;
162 topt.sparse_extract_index = false;
163 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
164 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l, topt);
166 for (Index_ x = 0; x < l; ++x) {
167 auto out = ext->fetch(vbuffer.data(), NULL);
168 output.minimum[x + s] = min_direct(out.value, out.number, otherdim, opt);
169 output.maximum[x + s] = max_direct(out.value, out.number, otherdim, opt);
170 }
171 }, dim, opt.num_threads);
172
173 } else {
174 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
175 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
177 for (Index_ x = 0; x < l; ++x) {
178 auto ptr = ext->fetch(buffer.data());
179 output.minimum[x + s] = min_direct(ptr, otherdim, opt);
180 output.maximum[x + s] = max_direct(ptr, otherdim, opt);
181 }
182 }, dim, opt.num_threads);
183 }
184}
185
186template<typename Value_, typename Index_, typename Output_>
187void range_running(bool row, const tatami::Matrix<Value_, Index_>& mat, RangeBuffers<Output_>& output, const RangeOptions<Output_>& opt) {
188 const auto dim = (row ? mat.nrow() : mat.ncol());
189 const auto otherdim = (row ? mat.ncol() : mat.nrow());
190 const bool is_sparse = mat.is_sparse();
191
192 const bool do_parallel = opt.num_threads > 1;
193 std::optional<std::vector<std::optional<std::vector<Output_> > > > all_partial_min, all_partial_max;
194 if (do_parallel) {
195 all_partial_min.emplace(sanisizer::cast<I<decltype(all_partial_min->size())> >(opt.num_threads - 1));
196 all_partial_max.emplace(sanisizer::cast<I<decltype(all_partial_max->size())> >(opt.num_threads - 1));
197 }
198
199 if (otherdim == 0) {
200 std::fill_n(output.minimum, dim, opt.minimum_placeholder);
201 std::fill_n(output.maximum, dim, opt.maximum_placeholder);
202 return;
203 }
204
205 const auto nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
206 Output_* min_ptr;
207 Output_* max_ptr;
208 std::optional<std::vector<Output_> > cur_min, cur_max;
209 if (!do_parallel) {
210 min_ptr = output.minimum;
211 max_ptr = output.maximum;
212 } else {
213 if (thread == 0) {
214 min_ptr = output.minimum;
215 max_ptr = output.maximum;
216 } else {
217 cur_min.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
218 cur_max.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
219 min_ptr = cur_min->data();
220 max_ptr = cur_max->data();
221 }
222 }
223
224 if (is_sparse) {
225 tatami::Options topt;
226 topt.sparse_ordered_index = false;
227 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l, topt);
230
231 // We pretend to start at one structural non-zero for every dimension element.
232 // This is because the first iteration effectively populates 'min_ptr/max_ptr' as a dense vector.
233 // So even a structural zero is already considered here, being treated as a structural non-zero with a value of zero.
235
236 for (Index_ x = 0; x < l; ++x) {
237 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
238
239 // For the first observed vector in each thread, we can optimize it a little as we don't need to read existing min/max.
240 if (x == 0) {
241 // We treat the first extracted row/column as a dense vector, expanding it with all of the zeros.
242 // For non-main threads, our thread-local buffer is already zeroed so no need to explicitly do this.
243 if (!do_parallel || thread == 0) {
244 std::fill_n(min_ptr, dim, 0);
245 std::fill_n(max_ptr, dim, 0);
246 }
247 for (Index_ i = 0; i < out.number; ++i) {
248 const auto val = out.value[i];
249 const auto idx = out.index[i];
250 min_ptr[idx] = val;
251 max_ptr[idx] = val;
252 }
253 } else {
254 for (Index_ i = 0; i < out.number; ++i) {
255 const auto val = out.value[i];
256 const auto idx = out.index[i];
257 auto& min_current = min_ptr[idx];
258 min_current = std::min(min_current, val);
259 auto& max_current = max_ptr[idx];
260 max_current = std::max(max_current, val);
261 ++nonzeros[idx];
262 }
263 }
264 }
265
266 for (Index_ d = 0; d < dim; ++d) {
267 if (l > nonzeros[d]) {
268 auto& min_current = min_ptr[d];
269 min_current = std::min(min_current, static_cast<Output_>(0));
270 auto& max_current = max_ptr[d];
271 max_current = std::max(max_current, static_cast<Output_>(0));
272 }
273 }
274
275 } else {
276 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
278
279 for (Index_ x = 0; x < l; ++x) {
280 auto ptr = ext->fetch(buffer.data());
281
282 // For the first observed vector in each thread, we can optimize it a little as we don't need to read existing min/max.
283 if (x == 0) {
284 std::copy_n(ptr, dim, min_ptr);
285 std::copy_n(ptr, dim, max_ptr);
286 } else {
287 for (Index_ i = 0; i < dim; ++i) {
288 const auto val = ptr[i];
289 auto& min_current = min_ptr[i];
290 min_current = std::min(min_current, val);
291 auto& max_current = max_ptr[i];
292 max_current = std::max(max_current, val);
293 }
294 }
295 }
296 }
297
298 if (do_parallel) {
299 if (thread > 0) {
300 (*all_partial_min)[thread - 1] = std::move(cur_min);
301 (*all_partial_max)[thread - 1] = std::move(cur_max);
302 }
303 }
304 }, otherdim, opt.num_threads);
305
306 if (do_parallel) {
307 for (int u = 1; u < nused; ++u) {
308 const auto& cur_min = *((*all_partial_min)[u - 1]);
309 const auto& cur_max = *((*all_partial_max)[u - 1]);
310 for (Index_ d = 0; d < dim; ++d) {
311 // All threads would have processed at least one element,
312 // so we don't have to worry about dirty input buffers.
313 output.minimum[d] = std::min(output.minimum[d], cur_min[d]);
314 output.maximum[d] = std::max(output.maximum[d], cur_max[d]);
315 }
316 }
317 }
318}
338template<typename Value_, typename Index_, typename Output_>
340 if (mat.prefer_rows() == row) {
341 range_direct(row, mat, output, opt);
342 } else {
343 range_running(row, mat, output, opt);
344 }
345}
346
352template<typename Output_>
358 std::vector<Output_> minimum;
359
364 std::vector<Output_> maximum;
365};
366
382template<typename Value_, typename Index_, typename Output_ = Value_>
385 const auto dim = (row ? mat.nrow() : mat.ncol());
387#ifdef TATAMI_STATS_TEST_DIRTY
388 , -1
389#endif
390 );
392#ifdef TATAMI_STATS_TEST_DIRTY
393 , -1
394#endif
395 );
396
397 RangeBuffers<Output_> buffers;
398 buffers.minimum = output.minimum.data();
399 buffers.maximum = output.maximum.data();
400 range(row, mat, buffers, opt);
401
402 return output;
403}
404
405}
406
407#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
constexpr Output_ default_maximum_placeholder()
Definition range.hpp:41
constexpr Output_ default_minimum_placeholder()
Definition range.hpp:27
void range(bool row, const tatami::Matrix< Value_, Index_ > &mat, RangeBuffers< Output_ > &output, const RangeOptions< Output_ > &opt)
Definition range.hpp:339
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:138
Output_ * maximum
Definition range.hpp:149
Output_ * minimum
Definition range.hpp:143
Options for range().
Definition range.hpp:54
Output_ minimum_placeholder
Definition range.hpp:65
Output_ maximum_placeholder
Definition range.hpp:71
int num_threads
Definition range.hpp:59
Results of range().
Definition range.hpp:353
std::vector< Output_ > maximum
Definition range.hpp:364
std::vector< Output_ > minimum
Definition range.hpp:358