tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
rss.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_SKIP_NAN_RSS_HPP
2#define TATAMI_STATS_SKIP_NAN_RSS_HPP
3
4#include "../utils.hpp"
5
6#include <vector>
7#include <cmath>
8#include <numeric>
9#include <limits>
10#include <algorithm>
11#include <cstddef>
12#include <optional>
13
14#include "tatami/tatami.hpp"
15#include "sanisizer/sanisizer.hpp"
16#include "quickstats/quickstats.hpp"
17
24namespace tatami_stats {
25
26namespace skip_nan {
27
31struct RssOptions {
36 int num_threads = 1;
37};
38
47template<typename Output_, typename Count_>
48struct RssBuffers {
53 Output_* mean;
54
59 Output_* rss;
60
65 Count_* count;
66};
67
71template<typename Value_, typename Index_, typename Output_, typename Count_>
72void rss_direct(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_, Count_>& output, const RssOptions& opt) {
73 const auto dim = (row ? mat.nrow() : mat.ncol());
74 const auto otherdim = (row ? mat.ncol() : mat.nrow());
75
76 if (mat.sparse()) {
77 tatami::Options topt;
78 topt.sparse_extract_index = false;
79
80 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
81 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l, topt);
83 quickstats::RssWorkspace<Output_> work;
84 for (Index_ x = 0; x < l; ++x) {
85 auto out = ext->fetch(vbuffer.data(), NULL);
86 tatami::copy_n(out.value, out.number, vbuffer.data());
87 const auto new_number = shift_nans(vbuffer.data(), out.number);
88 const Index_ new_total = otherdim - (out.number - new_number);
89 const auto res = quickstats::rss(new_total, new_number, vbuffer.data(), work);
90 output.mean[x + s] = res.mean;
91 output.rss[x + s] = res.rss;
92 output.count[x + s] = new_total;
93 }
94 }, dim, opt.num_threads);
95
96 } else {
97 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
98 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
100 quickstats::RssWorkspace<Output_> work;
101 for (Index_ x = 0; x < l; ++x) {
102 auto out = ext->fetch(buffer.data());
103 tatami::copy_n(out, otherdim, buffer.data());
104 const auto new_total = shift_nans(buffer.data(), otherdim);
105 const auto res = quickstats::rss(new_total, buffer.data(), work);
106 output.mean[x + s] = res.mean;
107 output.rss[x + s] = res.rss;
108 output.count[x + s] = new_total;
109 }
110 }, dim, opt.num_threads);
111 }
112}
113
114template<typename Value_, typename Index_, typename Output_, typename Count_>
115void rss_running(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_, Count_>& output, const RssOptions& opt) {
116 const auto dim = (row ? mat.nrow() : mat.ncol());
117 const auto otherdim = (row ? mat.ncol() : mat.nrow());
118 const bool is_sparse = mat.is_sparse();
119
120 if (otherdim == 0) {
121 std::fill_n(output.mean, dim, std::numeric_limits<Output_>::quiet_NaN());
122 std::fill_n(output.rss, dim, 0);
123 std::fill_n(output.count, dim, 0);
124 return;
125 }
126
127 assert(opt.num_threads > 0);
128 const bool do_parallel = opt.num_threads > 1;
129 std::optional<std::vector<std::optional<std::vector<Output_> > > > all_partial_mean, all_partial_rss;
130 std::optional<std::vector<std::optional<std::vector<Count_> > > > all_partial_count;
131 if (do_parallel) {
132 // -1, as we'll repurpose the output buffers to store the output of the first thread.
133 all_partial_rss.emplace(sanisizer::cast<I<decltype(all_partial_rss->size())> >(opt.num_threads - 1));
134 all_partial_mean.emplace(sanisizer::cast<I<decltype(all_partial_mean->size())> >(opt.num_threads));
135 all_partial_count.emplace(sanisizer::cast<I<decltype(all_partial_mean->size())> >(opt.num_threads));
136 }
137
138 std::fill_n(output.rss, dim, 0);
139 std::fill_n(output.mean, dim, 0);
140 std::fill_n(output.count, dim, 0);
141
142 const int nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
143 Output_* rss_ptr;
144 Output_* mean_ptr;
145 Count_* count_ptr;
146 std::optional<std::vector<Output_> > cur_rss, cur_mean;
147 std::optional<std::vector<Count_> > cur_count;
148
149 if (!do_parallel) {
150 // Storing mean and RSS directly in the output vector to cut down two allocations if we're not working in parallel.
151 rss_ptr = output.rss;
152 mean_ptr = output.mean;
153 count_ptr = output.count;
154 } else {
155 // Storing the partial RSS directly in the output vector to save ourselves an allocation if we're in the first thread.
156 // We can't do the same for the mean, though, as we need to keep the partial mean and the global mean separate for the reduction.
157 cur_mean.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
158 mean_ptr = cur_mean->data();
159 cur_count.emplace(tatami::cast_Index_to_container_size<std::vector<Count_> >(dim));
160 count_ptr = cur_count->data();
161 if (thread == 0) {
162 rss_ptr = output.rss;
163 } else {
164 cur_rss.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
165 rss_ptr = cur_rss->data();
166 }
167 }
168
169 if (is_sparse) {
170 tatami::Options topt;
171 topt.sparse_ordered_index = false;
172 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l, topt);
176
177 quickstats::RssRunningSparseSkip<Count_, Value_, Output_> runner(dim, mean_ptr, rss_ptr, nonzeros.data(), count_ptr);
178 for (Index_ x = 0; x < l; ++x) {
179 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
180 runner.add(
181 out.number,
182 out.value,
183 out.index,
184 [](std::size_t, const Value_ val) -> bool {
185 return std::isnan(val);
186 }
187 );
188 }
189 runner.finish();
190
191 } else {
192 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
194
195 quickstats::RssRunningDenseSkip<Count_, Value_, Output_> runner(dim, mean_ptr, rss_ptr, count_ptr);
196 for (Index_ x = 0; x < l; ++x) {
197 auto out = ext->fetch(buffer.data());
198 runner.add(
199 out,
200 [](std::size_t, const Value_ val) -> bool {
201 return std::isnan(val);
202 }
203 );
204 }
205 runner.finish();
206 }
207
208 // Moving results to the main containers.
209 if (do_parallel) {
210 (*all_partial_mean)[thread] = std::move(cur_mean);
211 (*all_partial_count)[thread] = std::move(cur_count);
212 if (thread > 0) {
213 (*all_partial_rss)[thread - 1] = std::move(cur_rss);
214 }
215 }
216 }, otherdim, opt.num_threads);
217 assert(nused > 0);
218
219 // Don't check nused > 1, as it's possible for do_parallel = true with nused = 1 if not all threads are used.
220 // This would cause us to leave output.mean and output.rss empty.
221 if (do_parallel) {
222 const auto& ap_mean = *all_partial_mean;
223 const auto& ap_rss = *all_partial_rss;
224 const auto& ap_count = *all_partial_count;
225
226 // Computing the global total.
227 for (int u = 0; u < nused; ++u) {
228 const auto& cur_count = *(ap_count[u]);
229 for (Index_ d = 0; d < dim; ++d) {
230 output.count[d] += cur_count[d];
231 }
232 }
233
234 // Computing the global mean from its components.
235 for (int u = 0; u < nused; ++u) {
236 const auto& cur_count = *(ap_count[u]);
237 const auto& cur_mean = *(ap_mean[u]);
238 for (Index_ d = 0; d < dim; ++d) {
239 if (cur_count[d] > 0) { // protect against NaN means at a count of 0.
240 const auto mult = static_cast<Output_>(cur_count[d]) / static_cast<Output_>(output.count[d]);
241 output.mean[d] += cur_mean[d] * mult;
242 }
243 }
244 }
245
246 for (Index_ d = 0; d < dim; ++ d) {
247 if (output.count[d] == 0) {
248 output.mean[d] = std::numeric_limits<Output_>::quiet_NaN();
249 }
250 }
251
252 // Combining the RSS. This time, we need to use the safe version as we don't know whether all elements were skipped in a thread.
253 for (int u = 0; u < nused; ++u) {
254 const auto& cur_count = *(ap_count[u]);
255 const auto& cur_mean = *(ap_mean[u]);
256 if (u == 0) {
257 for (Index_ d = 0; d < dim; ++d) {
258 output.rss[d] = quickstats::recenter_rss(cur_count[d], output.rss[d], cur_mean[d], output.mean[d]);
259 }
260 } else {
261 const auto& cur_rss = *(ap_rss[u - 1]);
262 for (Index_ d = 0; d < dim; ++d) {
263 output.rss[d] += quickstats::recenter_rss(cur_count[d], cur_rss[d], cur_mean[d], output.mean[d]);
264 }
265 }
266 }
267 }
268}
292template<typename Value_, typename Index_, typename Output_, typename Count_>
293void rss(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_, Count_>& output, const RssOptions& opt) {
294 if (mat.prefer_rows() == row) {
295 rss_direct(row, mat, output, opt);
296 } else {
297 rss_running(row, mat, output, opt);
298 }
299}
300
309template<typename Output_, typename Count_>
310struct RssResult {
315 std::vector<Output_> mean;
316
321 std::vector<Output_> rss;
322
327 std::vector<Count_> count;
328};
329
347template<typename Output_ = double, typename Count_, typename Value_, typename Index_>
350 const auto dim = (row ? mat.nrow() : mat.ncol());
351
353#ifdef TATAMI_STATS_TEST_DIRTY
354 , -1
355#endif
356 );
358#ifdef TATAMI_STATS_TEST_DIRTY
359 , -1
360#endif
361 );
363#ifdef TATAMI_STATS_TEST_DIRTY
364 , -1
365#endif
366 );
367
369 buffers.mean = output.mean.data();
370 buffers.rss = output.rss.data();
371 buffers.count = output.count.data();
372
373 rss(row, mat, buffers, opt);
374 return output;
375}
376
377}
378
379}
380
381#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual bool is_sparse() const=0
virtual std::unique_ptr< MyopicSparseExtractor< Value_, Index_ > > sparse(bool row, const Options &opt) const=0
void rss(bool row, const tatami::Matrix< Value_, Index_ > &mat, RssBuffers< Output_, Count_ > &output, const RssOptions &opt)
Definition rss.hpp:293
Functions to compute statistics from a tatami::Matrix.
Definition count.hpp:20
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)
Value_ * copy_n(const Value_ *const input, const Size_ n, Value_ *const output)
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 skip_nan::rss().
Definition rss.hpp:48
Output_ * mean
Definition rss.hpp:53
Output_ * rss
Definition rss.hpp:59
Count_ * count
Definition rss.hpp:65
Options for skip_nan::rss().
Definition rss.hpp:31
int num_threads
Definition rss.hpp:36
Results of skip_nan::rss().
Definition rss.hpp:310
std::vector< Count_ > count
Definition rss.hpp:327
std::vector< Output_ > mean
Definition rss.hpp:315
std::vector< Output_ > rss
Definition rss.hpp:321