tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
row_to_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_DENSE_MATRIX_SPARSE_ROW_ROW_TO_COLUMN_HPP
2#define TATAMI_MULT_DENSE_MATRIX_SPARSE_ROW_ROW_TO_COLUMN_HPP
3
4#include <cstddef>
5#include <vector>
6#include <algorithm>
7
8#include "tatami/tatami.hpp"
9#include "sanisizer/sanisizer.hpp"
10
11#include "../utils.hpp"
12#include "../../utils.hpp"
13
19namespace tatami_mult {
20
21/* See https://github.com/tatami-inc/test-multiplication/tree/master/sparse_row/dense_matrix
22 * for an explanation of the choice of algorithm.
23 */
24
46
65template<typename LeftValue_, typename LeftIndex_, typename RightColumns_, class GetRightRow_, typename Output_>
68 const RightColumns_ right_columns,
69 GetRightRow_ get_right_row,
70 Output_* const output,
72) {
73 const auto left_NR = left.nrow();
74 const auto common_dim = left.ncol();
75
76 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
77 auto ext = tatami::consecutive_extractor<true>(left, true, start, length);
80
81 if (options.block_size == 1) {
82 auto tmp_output = tatami::create_container_of_Index_size<std::vector<Output_> >(right_columns);
83 std::vector<LeftIndex_> left_empty;
84
85 for (LeftIndex_ lr = 0; lr < length; ++lr) {
86 const auto range = ext->fetch(vbuffer.data(), ibuffer.data());
87 if (range.number == 0) {
88 left_empty.push_back(lr);
89 continue;
90 }
91
92 for (LeftIndex_ x = 0; x < range.number; ++x) {
93 const auto rightrow = get_right_row(range.index[x]);
94 const Output_ mult = range.value[x];
95 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
96 tmp_output[rc] += mult * static_cast<Output_>(rightrow[rc]);
97 }
98 }
99
100 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
101 output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = tmp_output[rc];
102 }
103 std::fill(tmp_output.begin(), tmp_output.end(), 0);
104 }
105
106 if (left_empty.size()) {
107 // Zeroing the empty rows that we previously skipped. This is done with near-contiguous memory,
108 // so if there are many empty LHS rows, their special-casing should improve efficiency.
109 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
110 for (const auto lr : left_empty) {
111 output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = 0;
112 }
113 }
114 }
115
116 } else {
117 const auto max_block_rows = sanisizer::min(length, options.block_size);
118 std::vector<Output_> tmp_output(sanisizer::product<typename std::vector<Output_>::size_type>(max_block_rows, right_columns));
119
120 LeftIndex_ lr = 0;
121 while (lr < length) {
122 const LeftIndex_ lrnum = sanisizer::min(options.block_size, length - lr);
123 bool any_non_empty = false;
124 for (LeftIndex_ lrcopy = 0; lrcopy < lrnum; ++lrcopy) {
125 const auto range = ext->fetch(vbuffer.data(), ibuffer.data());
126 if (range.number == 0) {
127 continue;
128 }
129 any_non_empty = true;
130
131 for (LeftIndex_ x = 0; x < range.number; ++x) {
132 const auto rightrow = get_right_row(range.index[x]);
133 const Output_ mult = range.value[x];
134 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
135 tmp_output[sanisizer::nd_offset<std::size_t>(rc, right_columns, lrcopy)] += mult * static_cast<Output_>(rightrow[rc]);
136 }
137 }
138 }
139
140 // Now doing a blocked transposition.
141 // This is, in fact, the only purpose of the blocking here.
142 // We do this even if there were no non-empty LHS rows, because we have to zero the output array anyway.
143 RightColumns_ rc = 0;
144 while (rc < right_columns) {
145 const RightColumns_ rcend = rc + sanisizer::min(options.block_size, right_columns - rc);
146 for (LeftIndex_ lrcopy = 0; lrcopy < lrnum; ++lrcopy) {
147 for (auto rcopy = rc; rcopy < rcend; ++rcopy) {
148 const auto val = tmp_output[sanisizer::nd_offset<std::size_t>(rcopy, right_columns, lrcopy)];
149 output[sanisizer::nd_offset<std::size_t>(start + lr + lrcopy, left_NR, rcopy)] = val;
150 }
151 }
152 rc = rcend;
153 }
154
155 // Too much effort to track individual non-empty rows, but if they're all empty, we skip the zeroing.
156 // If a few are non-empty, a single memset call is probably faster anyway than splitting it up.
157 if (any_non_empty) {
158 std::fill_n(tmp_output.begin(), sanisizer::product_unsafe<std::size_t>(right_columns, lrnum), 0);
159 }
160
161 lr += lrnum;
162 }
163 }
164 }, left_NR, options.num_threads);
165}
166
187template<typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
191 Output_* const output,
193) {
194 const auto common_dim = left.ncol();
197 const auto right_NC = right.ncol();
198 populate_dense_buffers(true, common_dim, right_NC, right, right_buffers, right_ptrs, options.num_threads);
199
201 left,
202 right_NC,
203 [&](const LeftIndex_ cd) -> const RightValue_* {
204 return right_ptrs[cd];
205 },
206 output,
207 options
208 );
209}
210
211}
212
213#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Multiplication of tatami matrices.
Definition column_to_column.hpp:19
void multiply_sparse_row_with_dense_row_matrix_to_column_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const RightColumns_ right_columns, GetRightRow_ get_right_row, Output_ *const output, const MultiplySparseRowWithDenseRowMatrixToColumnOutputOptions &options)
Definition row_to_column.hpp:66
int parallelize(Function_ fun, const Index_ tasks, const int workers)
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)
Options for multiply_sparse_row_with_dense_row_matrix_to_column_output().
Definition row_to_column.hpp:28