tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
column_to_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_SPARSE_MATRIX_SPARSE_ROW_COLUMN_TO_COLUMN_HPP
2#define TATAMI_MULT_SPARSE_MATRIX_SPARSE_ROW_COLUMN_TO_COLUMN_HPP
3
4#include <cstddef>
5#include <vector>
6
7#include "tatami/tatami.hpp"
8#include "sanisizer/sanisizer.hpp"
9
10#include "../utils.hpp"
11#include "../../utils.hpp"
12#include "../../sparse_dot_product.hpp"
13
19namespace tatami_mult {
20
21/* See https://github.com/tatami-inc/test-multiplication/tree/master/sparse_row/sparse_matrix
22 * for an explanation of the choice of algorithm.
23 */
24
41
63template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
67 Output_* const output,
69) {
70 const auto left_NR = left.nrow();
71 const auto common_dim = left.ncol();
72 const auto right_NC = right.ncol();
73
77 populate_sparse_buffers(false, right_NC, common_dim, right, right_vbuffers, right_ibuffers, right_ranges, options.num_threads);
78
79 // If there are any empty RHS columns, we only iterate over the non-empty ones in the loop for each LHS row.
80 auto right_non_empty = filter_non_empty_sparse(
81 right_ranges,
82 [&](const RightIndex_ rc) -> void {
83 std::fill_n(output + sanisizer::product_unsafe<std::size_t>(left_NR, rc), left_NR, 0);
84 }
85 );
86
87 if (options.block_size == 1) {
88 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
89 auto ext = tatami::consecutive_extractor<true>(left, true, start, length);
92
93 // We expand each sparse vector into a dense buffer for easy look-up in sparse_dot_product().
95
96 for (LeftIndex_ lr = 0; lr < length; ++lr) {
97 const auto lrange = ext->fetch(vbuffer.data(), ibuffer.data());
98 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
99 expanded[lrange.index[x]] = lrange.value[x];
100 }
101
102 auto loop_body = [&](RightIndex_ rc) -> void {
103 const auto rrange = right_ranges[rc];
104 output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = sparse_dot_product<accumulators_>(
105 rrange.number, // Implicit cast to size_t is safe, as per the tatami contract.
106 rrange.value,
107 rrange.index,
108 expanded.data(),
109 static_cast<Output_>(0)
110 );
111 };
112
113 if (right_non_empty.has_value()) {
114 for (const auto rc : *right_non_empty) {
115 loop_body(rc);
116 }
117 } else {
118 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
119 loop_body(rc);
120 }
121 }
122
123 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
124 expanded[lrange.index[x]] = 0;
125 }
126 }
127 }, left_NR, options.num_threads);
128 return;
129 }
130
131 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
132 auto ext = tatami::consecutive_extractor<true>(left, true, start, length);
133
134 const LeftIndex_ max_block_rows = sanisizer::min(length, options.block_size);
135 std::vector<std::vector<LeftValue_> > lvbuffers;
136 std::vector<std::vector<LeftIndex_> > libuffers;
137 std::vector<std::vector<LeftValue_> > expanded;
138 lvbuffers.reserve(max_block_rows);
139 libuffers.reserve(max_block_rows);
140 expanded.reserve(max_block_rows);
141 for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
142 lvbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
143 libuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftIndex_> >(common_dim));
144 expanded.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
145 }
147
148 LeftIndex_ lr = 0;
149 while (lr < length) {
150 // No point skipping the LHS rows with no structural non-zeros.
151 // We still need to set the corresponding entry of 'output' to zero, so we'd end up having to loop through the LHS rows anyway.
152 // We might as well just let it be set to zero naturally in the existing loop below.
153 const LeftIndex_ lr_num = sanisizer::min(options.block_size, length - lr);
154 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
155 auto lrange = ext->fetch(lvbuffers[lr_counter].data(), libuffers[lr_counter].data());
156 auto& curex = expanded[lr_counter];
157 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
158 curex[lrange.index[x]] = lrange.value[x];
159 }
160 lranges[lr_counter] = std::move(lrange);
161 }
162
163 // Deliberately iterating over the (non-empty) sparse RHS columns in the outer loop and the dense (expanded) LHS rows in the inner loop.
164 // This aims to keep the entirety of the dense LHS block in cache across multiple RHS columns, provided common_dim is small.
165 // If we did it the other way around, it would just be the same as the block_size == 1 case, but with more looping overhead.
166 auto loop_body = [&](RightIndex_ rc) -> void {
167 const auto rrange = right_ranges[rc];
168 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
169 const auto val = sparse_dot_product<accumulators_>(
170 rrange.number, // Implicit cast to size_t is safe, as per the tatami contract.
171 rrange.value,
172 rrange.index,
173 expanded[lr_counter].data(),
174 static_cast<Output_>(0)
175 );
176 output[sanisizer::nd_offset<std::size_t>(start + lr + lr_counter, left_NR, rc)] = val;
177 }
178 };
179
180 if (right_non_empty.has_value()) {
181 for (const auto rc : *right_non_empty) {
182 loop_body(rc);
183 }
184 } else {
185 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
186 loop_body(rc);
187 }
188 }
189
190 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
191 const auto& lrange = lranges[lr_counter];
192 auto& curex = expanded[lr_counter];
193 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
194 curex[lrange.index[x]] = 0;
195 }
196 }
197
198 lr += lr_num;
199 }
200 }, left_NR, options.num_threads);
201}
202
203}
204
205#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_sparse_column_matrix_to_column_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const tatami::Matrix< RightValue_, RightIndex_ > &right, Output_ *const output, const MultiplySparseRowWithSparseColumnMatrixToColumnOutputOptions &options)
Definition column_to_column.hpp:64
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)
Options for multiply_sparse_row_with_sparse_column_matrix_to_column_output().
Definition column_to_column.hpp:28