tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
column_to_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_DENSE_MATRIX_SPARSE_COLUMN_COLUMN_TO_COLUMN_HPP
2#define TATAMI_MULT_DENSE_MATRIX_SPARSE_COLUMN_COLUMN_TO_COLUMN_HPP
3
4#include <cstddef>
5#include <vector>
6#include <optional>
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_column/dense_matrix
22 * for an explanation of the choice of algorithm.
23 */
24
41
42
43
44
62template<typename LeftValue_, typename LeftIndex_, typename RightColumns_, class GetRightColumn_, typename Output_>
65 const RightColumns_ right_columns,
66 GetRightColumn_ get_right_column,
67 Output_* const output,
69) {
70 const auto left_NR = left.nrow();
71 const auto common_dim = left.ncol();
72
73 const bool do_parallel = options.num_threads > 1;
74 std::optional<std::vector<std::optional<std::vector<Output_> > > > tmp_results;
75 if (do_parallel) {
76 tmp_results.emplace(sanisizer::cast<I<decltype(tmp_results->size())> >(options.num_threads - 1));
77 }
78
79 // Product must fit in a size_t in order for output to have been allocated correctly in the first place.
80 // Technically, right_columns could be larger than a size_t if left_NR == 0, but the product after wraparound would still be zero, so it's fine.
81 std::fill_n(output, sanisizer::product<std::size_t>(left_NR, right_columns), 0);
82
83 const auto num_used = tatami::parallelize([&](int t, LeftIndex_ start, LeftIndex_ length) -> void {
84 auto ext = tatami::consecutive_extractor<true>(left, false, start, length);
85
86 std::optional<std::vector<Output_> > tmp_output;
87 Output_* outptr;
88 if (!do_parallel || t == 0) {
89 outptr = output;
90 } else {
91 tmp_output.emplace(sanisizer::product<I<decltype(tmp_output->size())> >(left_NR, right_columns));
92 outptr = tmp_output->data();
93 }
94
95 if (options.block_size == 1) {
98 for (LeftIndex_ cd = 0; cd < length; ++cd) {
99 const auto range = ext->fetch(vbuffer.data(), ibuffer.data());
100 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
101 const Output_ mult = get_right_column(rc)[start + cd];
102 for (LeftIndex_ x = 0; x < range.number; ++x) {
103 outptr[sanisizer::nd_offset<std::size_t>(range.index[x], left_NR, rc)] += mult * static_cast<Output_>(range.value[x]);
104 }
105 }
106 }
107
108 } else {
109 std::vector<std::vector<LeftValue_> > left_vbuffers;
110 std::vector<std::vector<LeftIndex_> > left_ibuffers;
111 std::vector<tatami::SparseRange<LeftValue_, LeftIndex_> > left_ranges;
112 std::vector<LeftIndex_> left_non_empty;
113 {
114 const LeftIndex_ max_block_cols = sanisizer::min(length, options.block_size);
115 left_vbuffers.reserve(max_block_cols);
116 left_ibuffers.reserve(max_block_cols);
117 for (LeftIndex_ cd = 0; cd < max_block_cols; ++cd) {
118 left_vbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(left_NR));
119 left_ibuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftIndex_> >(left_NR));
120 }
121 sanisizer::resize(left_ranges, max_block_cols);
122 }
123
124 LeftIndex_ cd = 0;
125 while (cd < length) {
126 // We only consider the LHS rows with at least one structural non-zero.
127 // Thus, our block consists of 'options.block_size' non-empty LHS rows, rather than fixed row-wise chunks of the LHS matrix.
128 // This ensures that we don't waste iterations on LHS rows that will only have zeros in the output matrix (and are filled as such).
129 const auto left_block_info = fetch_non_empty_sparse_block(
130 *ext,
131 left_vbuffers,
132 left_ibuffers,
133 left_ranges,
134 left_non_empty,
135 cd,
136 length,
137 options.block_size,
138 /* zero = */ [&](const LeftIndex_) -> void {} // buffers should already be zeroed.
139 );
140 const auto cd_num = left_block_info.num_non_empty;
141
142 // If the LHS rows are all non-empty, we can speed up the loops by just using a simple counter to get the column indices.
143 // Otherwise, we'll have to access the 'left_non_empty' vector to figure out the indices of each non-empty column.
144 if (left_block_info.all_non_empty) {
145 const LeftIndex_ cd_base = cd + start;
146 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
147 const auto rightcol = get_right_column(rc);
148 for (LeftIndex_ cd_counter = 0; cd_counter < cd_num; ++cd_counter) {
149 const auto& currange = left_ranges[cd_counter];
150 const Output_ mult = rightcol[cd_base + cd_counter];
151 for (LeftIndex_ x = 0; x < currange.number; ++x) {
152 outptr[sanisizer::nd_offset<std::size_t>(currange.index[x], left_NR, rc)] += mult * static_cast<Output_>(currange.value[x]);
153 }
154 }
155 }
156
157 } else {
158 for (auto& cdne : left_non_empty) {
159 cdne += start;
160 }
161 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
162 const auto rightcol = get_right_column(rc);
163 for (LeftIndex_ cd_counter = 0; cd_counter < cd_num; ++cd_counter) {
164 const auto& currange = left_ranges[cd_counter];
165 const Output_ mult = rightcol[left_non_empty[cd_counter]];
166 for (LeftIndex_ x = 0; x < currange.number; ++x) {
167 outptr[sanisizer::nd_offset<std::size_t>(currange.index[x], left_NR, rc)] += mult * static_cast<Output_>(currange.value[x]);
168 }
169 }
170 }
171 }
172
173 cd = left_block_info.position;
174 }
175 }
176
177 if (do_parallel && t > 0) {
178 (*tmp_results)[t - 1] = std::move(tmp_output);
179 }
180 }, common_dim, options.num_threads);
181
182 if (do_parallel) {
183 for (int u = 1; u < num_used; ++u) {
184 const auto& tmp = *((*tmp_results)[u - 1]);
185 const auto N = tmp.size();
186 for (I<decltype(N)> x = 0; x < N; ++x) {
187 output[x] += tmp[x];
188 }
189 }
190 }
191}
192
213template<typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
217 Output_* const output,
219) {
220 const auto right_NC = right.ncol();
223 const auto common_dim = left.ncol();
224 populate_dense_buffers(false, right_NC, common_dim, right, right_buffers, right_ptrs, options.num_threads);
225
227 left,
228 right_NC,
229 [&](const RightIndex_ rc) -> const RightValue_* {
230 return right_ptrs[rc];
231 },
232 output,
233 options
234 );
235}
236
237}
238
239#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Multiplication of tatami matrices.
Definition column_to_column.hpp:19
void multiply_sparse_column_with_dense_column_matrix_to_column_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const RightColumns_ right_columns, GetRightColumn_ get_right_column, Output_ *const output, const MultiplySparseColumnWithDenseColumnMatrixToColumnOutputOptions &options)
Definition column_to_column.hpp:63
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_column_with_dense_column_matrix_to_column_output().
Definition column_to_column.hpp:28