tatami_stats
Matrix statistics for tatami
Loading...
Searching...
No Matches
rss.hpp
Go to the documentation of this file.
1#ifndef TATAMI_STATS_RSS_HPP
2#define TATAMI_STATS_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
29struct RssOptions {
34 int num_threads = 1;
35};
36
43template<typename Output_>
44struct RssBuffers {
49 Output_* mean;
50
55 Output_* rss;
56};
57
61template<typename Value_, typename Index_, typename Output_>
62void rss_direct(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_>& output, const RssOptions& opt) {
63 const auto dim = (row ? mat.nrow() : mat.ncol());
64 const auto otherdim = (row ? mat.ncol() : mat.nrow());
65
66 if (mat.sparse()) {
67 tatami::Options topt;
68 topt.sparse_extract_index = false;
69
70 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
71 auto ext = tatami::consecutive_extractor<true>(mat, row, s, l, topt);
73 quickstats::RssWorkspace<Output_> work;
74 for (Index_ x = 0; x < l; ++x) {
75 auto out = ext->fetch(vbuffer.data(), NULL);
76 const auto res = quickstats::rss(otherdim, out.number, out.value, work);
77 output.mean[x + s] = res.mean;
78 output.rss[x + s] = res.rss;
79 }
80 }, dim, opt.num_threads);
81
82 } else {
83 tatami::parallelize([&](int, Index_ s, Index_ l) -> void {
84 auto ext = tatami::consecutive_extractor<false>(mat, row, s, l);
86 quickstats::RssWorkspace<Output_> work;
87 for (Index_ x = 0; x < l; ++x) {
88 auto out = ext->fetch(buffer.data());
89 const auto res = quickstats::rss(otherdim, out, work);
90 output.mean[x + s] = res.mean;
91 output.rss[x + s] = res.rss;
92 }
93 }, dim, opt.num_threads);
94 }
95}
96
97template<typename Value_, typename Index_, typename Output_>
98void rss_running(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_>& output, const RssOptions& opt) {
99 const auto dim = (row ? mat.nrow() : mat.ncol());
100 const auto otherdim = (row ? mat.ncol() : mat.nrow());
101 const bool is_sparse = mat.is_sparse();
102
103 if (otherdim == 0) {
104 std::fill_n(output.mean, dim, std::numeric_limits<Output_>::quiet_NaN());
105 std::fill_n(output.rss, dim, 0);
106 return;
107 }
108
109 assert(opt.num_threads > 0);
110 const bool do_parallel = opt.num_threads > 1;
111 std::optional<std::vector<std::optional<std::vector<Output_> > > > all_partial_mean, all_partial_rss;
112 std::optional<std::vector<Index_> > all_partial_count;
113 if (do_parallel) {
114 // -1, as we'll repurpose the RSS output buffer to store the partial RSS of the first thread.
115 all_partial_rss.emplace(sanisizer::cast<I<decltype(all_partial_rss->size())> >(opt.num_threads - 1));
116 all_partial_mean.emplace(sanisizer::cast<I<decltype(all_partial_mean->size())> >(opt.num_threads));
117 all_partial_count.emplace(sanisizer::cast<I<decltype(all_partial_count->size())> >(opt.num_threads));
118 }
119
120 std::fill_n(output.rss, dim, 0);
121 std::fill_n(output.mean, dim, 0);
122
123 const int nused = tatami::parallelize([&](int thread, Index_ s, Index_ l) -> void {
124 Output_* rss_ptr;
125 Output_* mean_ptr;
126 std::optional<std::vector<Output_> > cur_rss, cur_mean;
127
128 if (!do_parallel) {
129 // Storing mean and RSS directly in the output vector to cut down two allocations if we're not working in parallel.
130 rss_ptr = output.rss;
131 mean_ptr = output.mean;
132 } else {
133 // Storing the partial RSS directly in the output vector to save ourselves an allocation if we're in the first thread.
134 // 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.
135 cur_mean.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
136 mean_ptr = cur_mean->data();
137 if (thread == 0) {
138 rss_ptr = output.rss;
139 } else {
140 cur_rss.emplace(tatami::cast_Index_to_container_size<std::vector<Output_> >(dim));
141 rss_ptr = cur_rss->data();
142 }
143 }
144
145 if (is_sparse) {
146 tatami::Options topt;
147 topt.sparse_ordered_index = false;
148 auto ext = tatami::consecutive_extractor<true>(mat, !row, s, l, topt);
152
153 quickstats::RssRunningSparse<Index_, Value_, Output_> runner(dim, mean_ptr, rss_ptr, nonzeros.data());
154 for (Index_ x = 0; x < l; ++x) {
155 auto out = ext->fetch(vbuffer.data(), ibuffer.data());
156 runner.add(out.number, out.value, out.index);
157 }
158 runner.finish();
159
160 } else {
161 auto ext = tatami::consecutive_extractor<false>(mat, !row, s, l);
163
164 quickstats::RssRunningDense<Value_, Output_> runner(dim, mean_ptr, rss_ptr);
165 for (Index_ x = 0; x < l; ++x) {
166 auto out = ext->fetch(buffer.data());
167 runner.add(out);
168 }
169 runner.finish();
170 }
171
172 if (do_parallel) {
173 (*all_partial_count)[thread] = l;
174 (*all_partial_mean)[thread] = std::move(cur_mean);
175 if (thread > 0) {
176 (*all_partial_rss)[thread - 1] = std::move(cur_rss);
177 }
178 }
179 }, otherdim, opt.num_threads);
180 assert(nused > 0);
181
182 // Don't check nused > 1, as it's possible for do_parallel = true with nused = 1 if not all threads are used.
183 // This would cause us to leave output.mean and output.rss empty.
184 if (do_parallel) {
185 const auto& ap_count = *all_partial_count;
186 const auto& ap_mean = *all_partial_mean;
187 const auto& ap_rss = *all_partial_rss;
188
189 // Computing the global mean. All ap_count is positive so we don't have to worry about cur_mean[d] being NaN.
190 for (int u = 0; u < nused; ++u) {
191 const Output_ mult = static_cast<Output_>(ap_count[u]) / static_cast<Output_>(otherdim);
192 const auto& cur_mean = *(ap_mean[u]);
193 for (Index_ d = 0; d < dim; ++d) {
194 output.mean[d] += cur_mean[d] * mult;
195 }
196 }
197
198 // Combining the RSS. We can use recenter_rss_unsafe() as we are guaranteed that cur_count > 0,
199 // as parallelize() will only ever split into non-empty ranges if those ranges are used.
200 for (int u = 0; u < nused; ++u) {
201 const auto cur_count = ap_count[u];
202 const auto& cur_mean = *(ap_mean[u]);
203 if (u == 0) {
204 for (Index_ d = 0; d < dim; ++d) {
205 output.rss[d] = quickstats::recenter_rss_unsafe(cur_count, output.rss[d], cur_mean[d], output.mean[d]);
206 }
207 } else {
208 const auto& cur_rss = *(ap_rss[u - 1]);
209 for (Index_ d = 0; d < dim; ++d) {
210 output.rss[d] += quickstats::recenter_rss_unsafe(cur_count, cur_rss[d], cur_mean[d], output.mean[d]);
211 }
212 }
213 }
214 }
215}
237template<typename Value_, typename Index_, typename Output_>
238void rss(bool row, const tatami::Matrix<Value_, Index_>& mat, RssBuffers<Output_>& output, const RssOptions& opt) {
239 if (mat.prefer_rows() == row) {
240 rss_direct(row, mat, output, opt);
241 } else {
242 rss_running(row, mat, output, opt);
243 }
244}
245
252template<typename Output_>
253struct RssResult {
258 std::vector<Output_> mean;
259
264 std::vector<Output_> rss;
265};
266
282template<typename Output_ = double, typename Value_, typename Index_>
284 RssResult<Output_> output;
285 const auto dim = (row ? mat.nrow() : mat.ncol());
287#ifdef TATAMI_STATS_TEST_DIRTY
288 , -1
289#endif
290 );
292#ifdef TATAMI_STATS_TEST_DIRTY
293 , -1
294#endif
295 );
296
297 RssBuffers<Output_> buffers;
298 buffers.mean = output.mean.data();
299 buffers.rss = output.rss.data();
300
301 rss(row, mat, buffers, opt);
302 return output;
303}
304
305}
306
307#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
Functions to compute statistics from a tatami::Matrix.
Definition count.hpp:20
void rss(bool row, const tatami::Matrix< Value_, Index_ > &mat, RssBuffers< Output_ > &output, const RssOptions &opt)
Definition rss.hpp:238
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 rss().
Definition rss.hpp:44
Output_ * mean
Definition rss.hpp:49
Output_ * rss
Definition rss.hpp:55
Options for rss().
Definition rss.hpp:29
int num_threads
Definition rss.hpp:34
Results of rss().
Definition rss.hpp:253
std::vector< Output_ > rss
Definition rss.hpp:264
std::vector< Output_ > mean
Definition rss.hpp:258