KASKADE 7 development version
Loading...
Searching...
No Matches
threadedMatrixChunks.hh
Go to the documentation of this file.
1
2/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
3/* */
4/* This file is part of the library KASKADE 7 */
5/* https://www.zib.de/research/projects/kaskade7-finite-element-toolbox */
6/* */
7/* Copyright (C) 2025-2025 Zuse Institute Berlin */
8/* */
9/* KASKADE 7 is distributed under the terms of the ZIB Academic License. */
10/* see $KASKADE/academic.txt */
11/* */
12/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
13
14#ifndef THREADED_MATRIX_CHUNKS
15#define THREADED_MATRIX_CHUNKS
16
17#include <cmath>
18#include <iostream>
19#include <boost/timer/timer.hpp>
20
21#include "dune/common/config.h"
22#include <dune/istl/bcrsmatrix.hh>
23#include <dune/istl/operators.hh>
24
25#include "fem/firstless.hh"
26#include "fem/fixdune.hh"
31
32
33namespace Kaskade
34{
35 // forward declarations - tell doxygen to ignore those and refer to the original definition
37 template <class Entry, class Index>
38 class NumaBCRSMatrix;
39
40 template <class Scalar, class Index>
41 class MatrixAsTriplet;
42
43 template <class Target, class Source, class RowIndices, class ColIndices>
44 Target submatrix(Source const& A, RowIndices const& ri, ColIndices const& ci);
46
47
48 namespace ThreadedMatrixDetail
49 {
50 // Computes for each row the number of entries in that row. The count is stored in the
51 // vector rowCount, which has to be big enough.
52 template <class Entry, class Allocator, class Index>
53 void getRowCount(Dune::BCRSMatrix<Entry,Allocator> const& matrix, bool isTransposed, std::vector<Index>& rowCount)
54 {
55 for (auto ri=matrix.begin(); ri!=matrix.end(); ++ri)
56 if (isTransposed)
57 for (auto ci=ri->begin(); ci!=ri->end(); ++ci)
58 ++rowCount[ci.index()];
59 else
60 rowCount[ri.index()] = ri->size();
61 }
62
63 template <class Entry, class Index2, class Index>
64 void getRowCount(NumaBCRSMatrix<Entry,Index2> const& matrix, bool isTransposed, std::vector<Index>& rowCount)
65 {
66 for (auto ri=matrix.begin(); ri!=matrix.end(); ++ri)
67 if (isTransposed)
68 for (auto ci=ri->begin(); ci!=ri->end(); ++ci)
69 ++rowCount[ci.index()];
70 else
71 rowCount[ri.index()] = ri->size();
72 }
73
74 template <class Entry, class Index, class Index2>
75 void getRowCount(MatrixAsTriplet<Entry,Index2> const& matrix, bool isTransposed, std::vector<Index>& rowCount)
76 {
77 auto const& indices = isTransposed? matrix.cidx: matrix.ridx;
78 for (auto r: indices)
79 ++rowCount[r];
80 }
81
82
83 // Class with partial specialization for copying from a Dune ISTL sparse matrix into ThreadedMatrix Chunks.
84 // Supported modes of copying are (i) one-to-one direct copy (ii) transposed copy (iii) source is symmetric
85 // (superdiagonal part is never accessed).
86 template <class Entry, class Matrix, bool symmetric, bool transposed, class Index>
88 {
89 // The simple case: not symmetric, not transposed
90 static void init(Index first, Index last, Matrix const& matrix, std::vector<size_t,NumaAllocator<size_t>>& colStart,
91 std::vector<Index,NumaAllocator<Index>>& cols, std::vector<Entry,NumaAllocator<Entry>>& values,
92 std::vector<Index> const& /* nRowEntries */)
93 {
94 for (Index i=0; i<last-first; ++i)
95 {
96 Index j=0;
97 for (auto ci=matrix[first+i].begin(); ci!=matrix[first+i].end(); ++ci, ++j)
98 {
99 values[colStart[i]+j] = *ci;
100 cols[colStart[i]+j] = ci.index();
101 }
102 }
103 }
104 };
105
106 template <class Entry, class Matrix, bool transposed, class Index>
107 struct CopyMatrixToChunk<Entry, Matrix,true,transposed,Index>
108 {
109 // The hard case: symmetric with only lower triangular part to be accessed
110 static void init(Index first, Index last, Matrix const& matrix, std::vector<size_t,NumaAllocator<size_t>>& colStart,
111 std::vector<Index,NumaAllocator<Index>>& cols, std::vector<Entry,NumaAllocator<Entry>>& values,
112 std::vector<Index> const& nRowEntries)
113 {
114 // Symmetric case is a mixture of regular and transposed. First we insert the regular entries,
115 // subsequently the transposed ones.
116
117 // Start with regular entries (including the diagonal)
118 std::vector<Index> entriesInRow(last-first,0);
119 for (Index i=0; i<last-first; ++i)
120 {
121 Index j=0;
122 for (auto ci=matrix[first+i].begin(); ci!=matrix[first+i].end(); ++ci, ++j)
123 {
124 values[colStart[i]+j] = *ci;
125 cols[colStart[i]+j] = ci.index();
126 }
127 entriesInRow[i] = matrix[first+i].size();
128 assert(entriesInRow[i]<=nRowEntries[first+i]);
129 }
130
131 // Do the transposed entries (excluding the diagonal). Note that as we only want entries with
132 // column index >= first+1, we can start (for the transposed part) at row first+1
133 for (Index r=first+1; r!=matrix.N(); ++r)
134 for (auto c=matrix[r].begin(); c!=matrix[r].end(); ++c)
135 {
136 // get transposed indices
137 Index ridx = c.index();
138 if (ridx>=first && ridx<last)
139 {
140 Index cidx = r;
141
142 if (cidx > ridx) // take only superdiagonal entries
143 {
144 assert(ridx-first<colStart.size() && ridx-first < entriesInRow.size());
145 Index pos = colStart[ridx-first] + entriesInRow[ridx-first];
146 assert(pos<colStart[ridx-first+1]);
147 cols[pos] = cidx;
148 values[pos] = transpose(*c);
149 ++entriesInRow[ridx-first];
150 assert(entriesInRow[ridx-first]<=nRowEntries[ridx]);
151 }
152 }
153 }
154 }
155 };
156
157
158 template <class Entry, class Matrix, class Index>
159 struct CopyMatrixToChunk<Entry, Matrix,false,true,Index>
160 {
161 // The case of transposed, nonsymmetric supplied matrices.
162 static void init(Index first, Index last, Matrix const& matrix, std::vector<size_t,NumaAllocator<size_t>>& colStart,
163 std::vector<Index,NumaAllocator<Index>>& cols, std::vector<Entry,NumaAllocator<Entry>>& values,
164 std::vector<Index> const& nRowEntries)
165 {
166 // Essentially we have to exchange row and column indices. If we go through the matrix
167 // row by row, the entries of the transposed matrix to be stored in our CRS chunk come
168 // with random row indices but increasing column indices. Hence we can push the entries
169 // at the back of each row. We only have to remember where the current end of each row is.
170 std::vector<Index> entriesInRow(last-first,0);
171
172 for (auto r=matrix.begin(); r!=matrix.end(); ++r)
173 for (auto c=r->begin(); c!=r->end(); ++c)
174 {
175 // get transposed indices
176 Index ridx = c.index();
177 if (ridx>=first && ridx<last)
178 {
179 Index cidx = r.index();
180
181 assert(ridx-first<colStart.size() && ridx-first < entriesInRow.size());
182 Index pos = colStart[ridx-first] + entriesInRow[ridx-first];
183 assert(pos<colStart[ridx-first+1]);
184 cols[pos] = cidx;
185 values[pos] = transpose(*c);
186 ++entriesInRow[ridx-first];
187 assert(entriesInRow[ridx-first]<=nRowEntries[ridx]);
188 }
189 }
190 }
191 };
192
193 //-------------------------------------------------------------------------
194
195 // Class for copying matrix entries. If the entry is to be transposed during
196 // copying (e.g. because only the lower triangular part of the source block matrix
197 // has been stored), the matrix dimensions may change due to transposition
198 // (if they are not equal). This case is covered here - we know the entry is
199 // to be transposed.
200 template <class To, class From, class scalarsMatch = std::false_type>
202 {
203 static void copy(From const& from, To& to, bool isTransposed)
204 {
205 assert(isTransposed);
206 to = transpose(from);
207 }
208 };
209
210 // This specialization is for the case of different floating point types as scalar entries in the matrix blocks.
211 // Currently this is only implemented for blocks of quadratic shape and non transposed blocks.
212 template <class To, class From>
213 struct MatrixEntry<To, From, typename std::is_same<typename To::field_type, typename From::field_type>::type>
214 {
215 static void copy(From const& from, To& to, bool isTransposed)
216 {
217 assert(!isTransposed);
218 static_assert(To::rows == From::rows && To::cols == From::cols, "Mismatch of dimensions.");
219 for(int i=0;i<To::rows;++i)
220 for(int j=0;j<To::cols;++j)
221 to[i][j] = static_cast<typename To::field_type>(from[i][j]);
222 }
223 };
224
225 // The case of matching dimensions of source and target is covered here.
226 // Both cases can occur, transposition or not. Partial specialization of
227 // this handler class is necessary due to the runtime switch between
228 // transposed or not - this dynamic switch is only legal in C++ if the
229 // dimensions are the same in both cases.
230 template <class Entry>
231 struct MatrixEntry<Entry,Entry>
232 {
233 static void copy(Entry const& from, Entry& to, bool isTransposed)
234 {
235 if (isTransposed && from.rows > 1 && from.cols > 1 && from.rows != from.cols)
236 {
237 std::cerr << "Transposing rectangular Dune::FieldMatrix not implemented!" << '\n'
238 << "#rows=" << from.rows << ", #cols=" << from.cols << '\n';
239 exit(1);
240 }
241 if (isTransposed && from.rows == from.cols)
242// to = transpose(from);
243// the following transposing code also covers the case that
244// the memory locations of from and to are the same
245 {
246 for (int i=0;i<from.rows;i++)
247 {
248 to[i][i]=from[i][i];
249 for (int j=i+1;j<from.cols;j++)
250 {
251 typename Entry::field_type s=from[j][i];
252 to[j][i]=from[i][j];
253 to[i][j]=s;
254 }
255 }
256 }
257 else
258 to = from;
259 }
260 };
261
262
263 //-------------------------------------------------------------------------
264
270 template <class Index=size_t>
272 {
273 template <class IndexP>
275 public:
284 CRSChunkPatternInfo(Index first_, Index last_, Index cols_, bool symmetric, int node)
285 : firstRow(first_), lastRow(last_), cols(cols_), numaNode(node), symm(symmetric)
286 {}
287
291 template <class IndexP>
293 : firstRow(pi.firstRow), lastRow(pi.lastRow), cols(pi.cols)
294 , numaNode(pi.numaNode), symm(pi.symm)
295 {}
296
300 Index first() const { return firstRow; }
301
305 Index last() const { return lastRow; }
306
310 Index columns() const { return cols; }
311
315 int node() const { return numaNode; }
316
320 bool symmetric() const { return symm; }
321
322 protected:
323 Index firstRow, lastRow; // first and one behind last row
324 Index cols; // number of columns
325
326 private:
327 int numaNode;
328 bool symm;
329 };
330
331 //-------------------------------------------------------------------------
332
345 template <class Index=size_t>
347 {
348 public:
352 typedef std::vector<Index> IndexArray; // column indices are indexed by user-provided Index type
353 // We could use a Numa allocator here, but testing as of 2014-03-12 revealed that the performance is
354 // *much* worse with Numa allocators, even though the Numa allocator is actually faster in
355 // synthetic tests of a structure similar to here. This reason for this disparity is up to now unknown.
356
357
366 CRSChunkPatternCreator(Index firstRow, Index lastRow, Index ncols, bool symmetric, int node)
368 cols(lastRow-firstRow)
369 {
370 }
371
378 void reserve(Index nnzPerRow=8)
379 {
380 // perform allocation
381 for (auto& r: cols)
382 r.reserve(nnzPerRow);
383 }
384
388 void clear()
389 {
390 for (auto& r: cols)
391 {
392 r.clear();
393 r.shrink_to_fit();
394 }
395 }
396
412 template <class IterRow, class IterCol>
413 void addElements(IterRow fromRow, IterRow const toRow,
414 IterCol const fromCol, IterCol const toCol, bool colIsSorted=false)
415 {
416 // sort column indices if needed, and remove duplicates (set_union below does NOT remove duplicates...)
417 sortedCols.clear();
418 sortedCols.insert(end(sortedCols),fromCol,toCol);
419 if (!colIsSorted)
420 std::sort(begin(sortedCols),end(sortedCols));
421 sortedCols.erase(std::unique(begin(sortedCols),end(sortedCols)),end(sortedCols));
422
423 // For each row, merge the specified column indices into the column indices that are already present.
424 for ( ; fromRow != toRow; ++fromRow) // step through all affected rows
425 if (this->first() <= *fromRow && *fromRow < this->last()) // but treat only those that actually lie in our chunk
426 {
427 IndexArray& c = cols[*fromRow-this->first()];
428 auto top = this->symmetric()?
429 std::upper_bound(begin(sortedCols),end(sortedCols),*fromRow):
430 end(sortedCols); // omit superdiagonal elements if symmetric
431 tmp.resize(c.size() + std::distance(begin(sortedCols),top)); // reserve space for the union
432 tmp.erase(std::set_union(begin(c),end(c),begin(sortedCols),top, // compute the union
433 begin(tmp)),end(tmp)); // and cut the container at the proper end
434 std::swap(tmp,c); // write the sorted range into its target
435 }
436 // Note that instead of a temporary buffer tmp we could append the new column indices and use
437 // std::inplace_merge. This, however, uses a temporary buffer of its own behind the scenes, one that is held
438 // by the STL library implementation somewhere. It is highly probable that this memory is *not* located
439 // on our NUMA node, but frequently accessed from all nodes. Hence, false sharing is bound to occur with
440 // inplace_merge even if it performs proper locking.
441 }
442
447 {
448 for (Index i=0; i<cols.size(); ++i)
449 {
450 cols[i].resize(this->symmetric()? i+this->first()+1: columns);
451 std::iota(begin(cols[i]),end(cols[i]),static_cast<Index>(0));
452 }
453 }
454
459 IndexArray const& row(Index i) const { return cols[i-this->first()]; }
460
464 size_t nonzeroes() const
465 {
466 size_t nnz = 0;
467 for (auto const& r: cols)
468 nnz += r.size();
469 return nnz;
470 }
471
482 size_t balanceForward(size_t const covered, size_t const nnz, int chunks, std::vector<IndexArray>& moveRows);
483
494 size_t balanceBackward(size_t covered, size_t const nnz, int chunks, std::vector<IndexArray>& moveRows);
495
496 private:
497 std::vector<IndexArray> cols; // For each row a std::vector of column indices
498 IndexArray tmp; // some temporary buffer
499 IndexArray sortedCols; // some temporary buffer for sorted column indices
500 };
501
502 //-------------------------------------------------------------------------
503
509 template <class Index=size_t>
511 {
512 public:
513
518
525
529 template <class IndexP>
531 : CRSChunkPatternInfo<Index>(p)
532 , colStarts(p.colStarts)
533 , cols(p.cols.begin(),p.cols.end(),NumaAllocator<Index>(p.node()))
534 , nnzPerRow(p.nnzPerRow)
535 {}
536
552 template <class Expanded, class Condensed, class Matrix>
553 CRSChunkPattern(Index first, Index last, Expanded const& eIndices, Condensed const& cIndices,
554 Matrix const& mat, int node)
555 : CRSChunkPatternInfo<Index>(first,last,eIndices.size(),false,node)
556 , colStarts(last-first+1,0,NumaAllocator<size_t>(node)), cols(NumaAllocator<Index>(node))
557 {
558 cols.reserve(last-first); // prevent frequent reallocation (heuristic)
559
560 for (Index i=first; i<last; ++i) // scan all our rows
561 {
562 colStarts[i-first] = cols.size(); // note where this row starts
563
564 auto const& row = mat[eIndices[i]]; // get a handle to the column indices in that row
565
566 auto cend = row.end();
567 for (auto c = row.begin(); c!=cend; ++c) // step through all column entries in that row
568 {
569 Index ci = cIndices[c.index()]; // extract the condensed index
570 if (ci<eIndices.size()) // if that is in the condensed matrix range...
571 cols.push_back(ci); // ...include it
572 }
573 }
574
575 colStarts.back() = cols.size(); // sentinel
576
577 // investigate sparsity
578 nnzPerRow = this->last()==this->first()? 0 : nonzeroes() / (this->last()-this->first());
579 }
580
593 template <class Matrix>
594 CRSChunkPattern(Matrix const& matrix, bool isSymmetric, bool isTransposed,
595 Index firstRow, Index lastRow, bool symmetric, int node)
597 , colStarts(NumaAllocator<size_t>(node)), cols(NumaAllocator<Index>(node))
598 {
599 if (isSymmetric) // Make sure that transposed is only flagged if
600 isTransposed = false; // it's not symmetric.
601
602 // Compute start of row indices. Step through the supplied matrix and count all elements that
603 // fall in one of our rows.
604 std::vector<size_t,NumaAllocator<size_t>> rowCount(this->last()-this->first(),0,NumaAllocator<size_t>(node));
605
606 // Compute the row range of the supplied matrix we have to scan.
607 Index fromRow = this->first(), toRow = this->last();
608 if (isSymmetric && !symmetric) // If the supplied matrix is symmetrically stored but we are not, we have to
609 toRow = matrix.N(); // scan the later rows for the elements that appear in later columns here.
610 if (isTransposed) // If the supplied matrix is transposed, we have to scan all the rows
611 fromRow = 0, toRow = matrix.N(); // in order to cover all the columns in our range.
612
613 for (Index ri=fromRow; ri<toRow; ++ri)
614 {
615 auto cend = matrix[ri].end();
616 for (auto ci=matrix[ri].begin(); ci!=cend; ++ci)
617 {
618 Index r = ri, c = ci.index();
619 if (isTransposed) // supplied matrix is transposed, swap row and column indices
620 std::swap(r,c); // to get the row/col indices in our world
621 if (this->first()<=r && r<this->last() && (!symmetric || c<=r)) // yep, this element is in our chunk
622 ++rowCount[r-this->first()]; //
623 if (isSymmetric && !symmetric && c<r && this->first()<=c && c<this->last()) // if supplied matrix is stored symmetrically but we are not
624 ++rowCount[c-this->first()]; // copy truely subdiagonal entries to the upper triangle
625 }
626 }
627
628 // compute the partial sums of number of elements per row.
629 colStarts.resize(rowCount.size()+1,0);
630 std::partial_sum(rowCount.begin(),rowCount.end(),colStarts.begin()+1);
631
632 // Get space for all nonzeros
633 cols.resize(colStarts.back());
634
635 // Copy the column indices of elements.
636 for (Index ri=fromRow; ri<toRow; ++ri)
637 {
638 auto cend = matrix[ri].end();
639 for (auto ci=matrix[ri].begin(); ci!=cend; ++ci)
640 {
641 Index r = ri, c = ci.index();
642 if (isTransposed) // supplied matrix is transposed, swap row and column indices
643 std::swap(r,c); // to get the row/col indices in our world
644 if (this->first()<=r && r<this->last() && (!symmetric || c<=r)) // yep, this element is in our chunk
645 {
646 cols[colStarts[r-this->first()]+rowCount[r-this->first()]-1] = c; // enter the column index
647 --rowCount[r-this->first()]; // one less element to come
648 }
649 if (isSymmetric && !symmetric && c<r && this->first()<=c && c<this->last()) // if supplied matrix is stored symmetrically but we are not
650 {
651 cols[colStarts[c-this->first()]+rowCount[c-this->first()]-1] = r; // copy truely subdiagonal entries to the upper triangle
652 --rowCount[c-this->first()]; // one less element to come
653 }
654 }
655 }
656
657 // sort the column indices in each row
658 for (Index ri=0; ri<rowCount.size(); ++ri) {
659 assert(rowCount[ri] == 0);
660 std::sort(cols.begin()+colStarts[ri],cols.begin()+colStarts[ri+1]);
661 }
662
663 // investigate sparsity
664 nnzPerRow = this->last()==this->first()? 0 : nonzeroes() / (this->last()-this->first());
665 }
666
677 template <class Scalar, class Index2>
678 CRSChunkPattern(MatrixAsTriplet<Scalar,Index2> const& matrix, bool isSymmetric, bool isTransposed, // TODO: fuse with constructor above
679 Index firstRow, Index lastRow, bool symmetric, int node)
681 , colStarts(NumaAllocator<size_t>(node)), cols(NumaAllocator<Index>(node))
682 {
683 if (isSymmetric) // Make sure that transposed is only flagged if
684 isTransposed = false; // it's not symmetric.
685
686 // Compute start of row indices. Step through the supplied matrix and count all elements that
687 // fall in one of our rows.
688 std::vector<size_t,NumaAllocator<size_t>> rowCount(this->last()-this->first(),0,NumaAllocator<size_t>(node));
689
690 for (size_t i=0; i<matrix.ridx.size(); ++i)
691 {
692 Index r = matrix.ridx[i], c = matrix.cidx[i];
693 if (isTransposed) // supplied matrix is transposed, swap row and column indices
694 std::swap(r,c); // to get the row/col indices in our world
695 if (this->first()<=r && r<this->last() && (!symmetric || c<=r)) // yep, this element is in our chunk
696 ++rowCount[r-this->first()]; //
697 if (isSymmetric && !symmetric && c<r && this->first()<=c && c<this->last()) // if supplied matrix is stored symmetrically but we are not
698 ++rowCount[c-this->first()]; // copy truely subdiagonal entries to the upper triangle
699 }
700
701 // compute the partial sums of number of elements per row.
702 colStarts.resize(rowCount.size()+1,0);
703 std::partial_sum(rowCount.begin(),rowCount.end(),colStarts.begin()+1);
704
705 // Get space for all nonzeros
706 cols.resize(colStarts.back());
707
708 // Copy the column indices of elements.
709 for (size_t i=0; i<matrix.ridx.size(); ++i)
710 {
711 Index r = matrix.ridx[i], c = matrix.cidx[i];
712 if (isTransposed) // supplied matrix is transposed, swap row and column indices
713 std::swap(r,c); // to get the row/col indices in our world
714 if (this->first()<=r && r<this->last() && (!symmetric || c<=r)) // yep, this element is in our chunk
715 {
716 cols[colStarts[r-this->first()]+rowCount[r-this->first()]-1] = c; // enter the column index
717 --rowCount[r-this->first()]; // one less element to come
718 }
719 if (isSymmetric && !symmetric && c<r && this->first()<=c && c<this->last()) // if supplied matrix is stored symmetrically but we are not
720 {
721 cols[colStarts[c-this->first()]+rowCount[c-this->first()]-1] = r; // copy truely subdiagonal entries to the upper triangle
722 --rowCount[c-this->first()]; // one less element to come
723 }
724 }
725
726 // sort the column indices in each row
727 for (Index ri=0; ri<rowCount.size(); ++ri) // TODO: do this in parallel on our node?
728 {
729 assert(rowCount[ri] == 0);
730 auto first = cols.begin()+colStarts[ri];
731 auto last = cols.begin()+colStarts[ri+1];
732 std::sort(first,last); // sort in ascending order
733 last = std::unique(first,last); // duplicate entries may come from triplet matrices, remove
734 rowCount[ri] = last-first; // now we have the correct number of entries.
735 }
736
737 // Merging duplicate column indices in a row may have reduced the number of entries in that row,
738 // hence there may be a gap to the start of the next row. Compactify the column index array now.
739 for (Index ri=1; ri<rowCount.size(); ++ri)
740 {
741 Index newStart = colStarts[ri-1] + rowCount[ri-1];
742 if (newStart != colStarts[ri]) // entries need to be shifted
743 {
744 std::copy(cols.begin()+colStarts[ri],cols.begin()+colStarts[ri]+rowCount[ri],cols.begin()+newStart);
745 colStarts[ri] = newStart;
746 }
747 }
748 if (!rowCount.empty()) // set the sentinel to point just behind the last entry...
749 colStarts.back() = colStarts[rowCount.size()-1]+rowCount.back();
750 cols.erase(cols.begin()+colStarts.back(),cols.end()); // ...and drop the rubbish left over in the tail
751
752
753 // investigate sparsity
754 nnzPerRow = this->last()==this->first()? 0 : nonzeroes() / (this->last()-this->first());
755 }
756
760
761 // returns the column index of entry at position idx
762 Index col(size_t idx) const { return cols[idx]; }
763
768 size_t colStart(Index row) const { return colStarts[row]; }
769
774 typename std::vector<Index,NumaAllocator<Index>>::const_iterator colStartIterator(Index row) const
775 {
776 return cols.begin()+colStart(row);
777 }
778
787 size_t position(Index r, Index c) const
788 {
789 auto b = cols.begin();
790 r -= this->first();
791 assert(0 <= r && r+1 < colStarts.size());
792 auto p = std::lower_bound(b+colStarts[r],b+colStarts[r+1],c); // col indices are sorted in each row
793 if (p==b+colStarts[r+1] || *p!=c) // return sentinel in case the entry is not here
794 return std::numeric_limits<size_t>::max();
795 return p-b; // return offset
796 }
797
801 bool exists(Index r, Index c) const
802 {
803 return position(r,c) != std::numeric_limits<size_t>::max();
804 }
805
811 size_t storage() const { return cols.size(); }
812
818 size_t nonzeroes() const;
819
823 Index nonzeroesPerRow() const { return nnzPerRow; }
824
825 private:
826 template <class IndexP> // Provide internal access even if the index type
827 friend class CRSChunkPattern; // does not coincide.
828
829 // Raw data storage. The allocators used are NUMA allocators, guaranteeing local memory access.
830 std::vector<size_t,NumaAllocator<size_t>> colStarts; // memory positions are indexed by size_t (since there may be MANY)
831 std::vector<Index,NumaAllocator<Index>> cols; // column indices are indexed by user-provided Index type
832 Index nnzPerRow; // average number of nonzeroes per row (rounded)
833 };
834
835 //----------------------------------------------------------------------------------------------
836 //----------------------------------------------------------------------------------------------
837
838
839 template <class Arguments, class Operation>
841 {
842 public:
844 {
845 };
846
847 iterator begin(size_t) const
848 {
849 }
850
851 iterator end(size_t) const
852 {
853 }
854 };
855
856 //----------------------------------------------------------------------------------------------
857
868 template <class Entry, class Index=size_t>
870 {
871 using Self = CRSChunk<Entry,Index>;
872
873 template <class EntryA, class IndexA>
874 friend class CRSChunk;
875
876 public:
878
883
891 CRSChunk(int node)
892 : values(NumaAllocator<Entry>(node))
893 {
894 }
895
899 template <class EntryA, class IndexA,
900 typename = std::enable_if_t<std::is_convertible_v<EntryA,Entry>>>
901 CRSChunk(std::shared_ptr<CRSChunkPattern<Index>> const& pattern,
903 : pat(pattern)
904 , scatterMutex(4)
905 , values(c.values.begin(),c.values.end(),NumaAllocator<Entry>(c.pat->node()))
906 {
907 }
908
912 CRSChunk(std::shared_ptr<CRSChunkPattern<Index>> const& pattern_, Entry const& init)
913 : pat(pattern_)
914 , scatterMutex(4)
915 , values(pat->nonzeroes(),init,NumaAllocator<Entry>(pat->node()))
916 {
917 }
918
931 template <class Matrix>
932 CRSChunk(std::shared_ptr<CRSChunkPattern<Index>> const& pattern_, Matrix const& matrix,
933 bool isSymmetric, bool isTransposed)
934 : CRSChunk(pattern_,Entry(0))
935 {
936 typedef typename Matrix::block_type SuppliedEntry;
937
938 Index first = pat->first(), last = pat->last();
939
940 if (isSymmetric) // Make sure that transposed is only flagged if
941 isTransposed = false; // it's not symmetric.
942
943 // Compute the row range of the supplied matrix we have to scan.
944 Index fromRow = first, toRow = last;
945 if (isSymmetric && !pat->symmetric()) // If the supplied matrix is symmetrically stored but we are not, we have to
946 toRow = matrix.N(); // scan the later rows for the elements that appear in later columns here.
947 if (isTransposed) // If the supplied matrix is transposed, we have to scan all the rows
948 fromRow = 0, toRow = matrix.N(); // in order to cover all the columns in our range.
949
950 // Copy the column indices of elements.
951 for (Index ri=fromRow; ri<toRow; ++ri)
952 {
953 auto cend = matrix[ri].end();
954 for (auto ci=matrix[ri].begin(); ci!=cend; ++ci)
955 {
956 Index r = ri, c = ci.index();
957 if (isTransposed) // supplied matrix is transposed, swap row and column indices
958 std::swap(r,c); // to get the row/col indices in our world
959 if (first<=r && r<last && (!pat->symmetric() || c<=r)) // yep, this element is in our chunk
960 {
961 assert(pat->position(r,c) < values.size());
962 MatrixEntry<Entry,SuppliedEntry>::copy(*ci,values[pat->position(r,c)],isTransposed); // copy, transpose if needed
963 }
964 if (isSymmetric && !pat->symmetric() && c<r && first<=c && c<last) // if supplied matrix is stored symmetrically but we are not..
965 MatrixEntry<Entry,SuppliedEntry>::copy(*ci,values[pat->position(c,r)],true); // copy, transpose
966 }
967 }
968 }
969
981 template <class Scalar, class Index2>
982 CRSChunk(std::shared_ptr<CRSChunkPattern<Index>> const& pattern_, MatrixAsTriplet<Scalar,Index2> const& matrix, bool isSymmetric, bool isTransposed)
983 : CRSChunk(pattern_,Entry(0))
984 {
985 Index first = pat->first(), last = pat->last();
986
987 if (isSymmetric) // Make sure that transposed is only flagged if
988 isTransposed = false; // it's not symmetric.
989
990
991 // Copy the column indices of elements.
992 for (size_t i=0; i<matrix.ridx.size(); ++i)
993 {
994 Index r = matrix.ridx[i], c = matrix.cidx[i];
995 if (isTransposed) // supplied matrix is transposed, swap row and column indices
996 std::swap(r,c); // to get the row/col indices in our world
997 if (first<=r && r<last && (!pat->symmetric() || c<=r)) // yep, this element is in our chunk
998 {
999 assert(pat->position(r,c) < values.size());
1000 values[pat->position(r,c)] = matrix.data[i]; // copy (no transpose as triplet entries are scalar)
1001 }
1002 if (isSymmetric && !pat->symmetric() && c<r && first<=c && c<last) // if supplied matrix is stored symmetrically but we are not..
1003 values[pat->position(c,r)] = matrix.data[i]; // copy (no transpose as triplet entries are scalar)
1004 }
1005 }
1006
1020 template <class Matrix>
1021 CRSChunk(Matrix const& matrix, bool isSymmetric, bool isTransposed,
1022 Index firstRow, Index lastRow, bool symmetric, int node)
1023 : CRSChunk(std::make_shared<CRSChunkPattern<Index>>(matrix,isSymmetric,isTransposed,
1024 firstRow,lastRow,symmetric,node),
1025 matrix,isSymmetric,isTransposed)
1026 {}
1027
1040 template <class Expanded, class Condensed, class Matrix>
1041 CRSChunk(std::shared_ptr<CRSChunkPattern<Index>> const& pattern_,
1042 Expanded const& eIndices, Condensed const& cIndices, Matrix const& matrix)
1043 : CRSChunk(pattern_,Entry(0))
1044 {
1045 Index first = pat->first(), last = pat->last();
1046 for (Index r=first; r<last; ++r)
1047 {
1048 auto row = matrix[eIndices[r]];
1049 auto p = begin(values) + pat->colStart(r-first);
1050 for (auto c=row.begin(); c!=row.end(); ++c)
1051 if (cIndices[c.index()]<eIndices.size())
1052 {
1053 *p = *c;
1054 assert(!std::isnan(*p));
1055 ++p;
1056 }
1057
1058 assert(p==begin(values)+pat->colStart(r-first+1));
1059 }
1060 }
1061
1065
1069 Self& operator=(Self const& c) = default;
1070
1074 template <class Value>
1075 Self& operator=(Value const& a)
1076 {
1077 std::fill(begin(values),end(values),a);
1078 return *this;
1079 }
1080
1081
1085 template <class Arguments, class Operation>
1087 {
1088 Index first = pat->first();
1089 for (Index r=0; r<pat->last()-first; ++r)
1090 {
1091 auto eend = e.end(r);
1092 auto ci = pat->colStartIterator(r);
1093 auto cend = pat->colStartIterator(r+1);
1094 auto vi = begin(values)+pat->colStart(r);
1095 for (auto ei = e.begin(r); ei != eend; ++ei) // step through the expression row
1096 {
1097 auto pos = std::find(ci,cend,ei.index()); // find our entry
1098 assert(pos != cend); // our pattern must be a superset of the expression pattern
1099 vi += pos-ci; // advance to found entry
1100 ci = pos;
1101 *vi = *ei; // assign
1102 }
1103 }
1104 }
1105
1110 template <class Domain, class Range>
1111 Scalar apply(Scalar a, Domain const& x, Range& y, bool initialize) const
1112 {
1113 Scalar dp = 0;
1114 auto const& p = *pat;
1115 Index first = p.first();
1116 Index last = p.last();
1117
1118 // Perform matrix vector multiplication. Step through all rows in our chunk...
1119 // On Numa nodes with several cores, one may think that using multiple threads
1120 // on this node could improve the performance. Alas, this appears not to be the
1121 // case as of 2014-05 (both aged AMD Opteron 32cores/8nodes and a newer 32/4 Intel
1122 // Xeon). It seems that a single thread saturates the memory channel(s).
1123 // Hence we stay with the sequential version here.
1124 for (Index row=first; row<last; ++row)
1125 {
1126 // and compute the scalar product of (sparse) row and vector
1127 typename Range::block_type z(0);
1128
1129 size_t jEnd = p.colStart(row+1-first);
1130 for (size_t j=p.colStart(row-first); j<jEnd; ++j)
1131 if (Entry::rows==1 && Entry::cols==1)
1132 z.axpy(values[j][0][0],x[p.col(j)]); // this is necessary as otherwise scalar matrix entries and
1133 else // vecotr-valued rhs entries do not work together
1134 values[j].umv(x[p.col(j)],z);
1135
1136 // scale result as requested
1137 z *= a;
1138 if (initialize) y[row] = z;
1139 else y[row] += z;
1140
1141 // accumulate duality product
1142 if (Entry::rows==Entry::cols && row<x.size())
1143 dp += y[row]*x[row];
1144 }
1145
1146 return dp;
1147 }
1148
1153 template <class Domain, class Range>
1154 void applyTransposed(Scalar a, Domain const& x, Range& y) const
1155 {
1156 auto const& p = *pat;
1157 Index first = p.first(); // first row in our chunk
1158 Index last = p.last(); // last row in our chunk
1159
1160 assert(x.size()>=last);
1161
1162 // We compute y_i = a sum_j A_ji^T x_j, with j in the outer loop
1163 for (Index j=first; j<last; ++j) // looping over the rows of A, i.e. the columns of A^T
1164 {
1165 size_t jEnd = p.colStart(j+1-first);
1166 for (size_t k=p.colStart(j-first); k<jEnd; ++k) // looping over the columns in the row
1167 {
1168 auto i = p.col(k); // the column index in A, i.e. the row index in A^T
1169 if (Entry::rows==1 && Entry::cols==1)
1170 y[i] += a*values[k][0][0] * x[j]; // this is necessary as otherwise scalar matrix entries and
1171 else // vecotr-valued rhs entries do not work together
1172 values[k].usmtv(a,x[j],y[i]); // y_i += a * (A^T)_ij x_j = a * (A_ji)^T x_j
1173 }
1174 }
1175 }
1176
1185 template <class Domain, class Range>
1186 void gatherMirrored(Scalar a, Domain const& x, Range& y, CRSChunk<Entry,Index> const& block, bool subdiagonal, bool initToZero) const
1187 {
1188 auto const& p = *block.pat;
1189 Index firstRow = p.first();
1190 Index lastRow = p.last();
1191 Index firstCol = pat->first();
1192 Index lastCol = pat->last();
1193
1194 assert(lastRow<=y.size());
1195
1196 if (initToZero)
1197 for (Index row=firstRow; row<lastRow; ++row)
1198 y[row] = 0;
1199
1200 for (Index row=firstRow; row<lastRow; ++row)
1201 {
1202 auto const& xrow = x[row];
1203
1204 auto cend = p.colStartIterator(row+1-firstRow);
1205 auto ci = firstCol==0? // We consider all elements with column index in our
1206 p.colStartIterator(row-firstRow): // row chunk. Compute the starting point by binary search
1207 std::lower_bound(p.colStartIterator(row-firstRow),cend,firstCol); // (or used the shortcut if we know we start from the beginning).
1208 auto vi = block.values.begin() + (ci-p.colStartIterator(0));
1209 if (subdiagonal)
1210 for ( ; ci!=cend && *ci<lastCol && *ci<row; ++ci, ++vi) // Then step through the row until we hit the upper bound,
1211 vi->usmtv(a,xrow,y[*ci]); // which is either the upper row range end or the diagonal,
1212 else // depending on the matrix symmetry (encoded in subdiagonal).
1213 for ( ; ci!=cend && *ci<lastCol; ++ci, ++vi)
1214 vi->usmtv(a,xrow,y[*ci]);
1215 }
1216 }
1217
1221 CRSChunkPattern<Index> const& pattern() const { return *pat; }
1222
1227 auto valStartIterator(Index row)
1228 {
1229 return values.begin()+pat->colStart(row);
1230 }
1231
1235 template <class LMIterator>
1236 void scatter(LMIterator first, LMIterator last)
1237 {
1238 Index firstRow = pat->first();
1239 Index lastRow = pat->last();
1240
1241 if (first==last || firstRow==lastRow) // empty data or empty chunk
1242 return; // -> nothing to do
1243
1244 Index nnzPerRow = pat->nonzeroesPerRow();
1245
1246 // scatter into uniform ranges
1247 // TODO: does it make sense to parallelize this within each NUMA domain? Right now it
1248 // is not parallelized, only prepared.
1249 Index n = scatterMutex.size();
1250 for (Index i=0; i<n; ++i)
1251 {
1252 boost::lock_guard<boost::mutex> lock(scatterMutex[i].get());
1253
1254 Index rowRangeStart = uniformWeightRangeStart(i, n,lastRow-firstRow)+firstRow;
1255 Index rowRangeEnd = uniformWeightRangeStart(i+1,n,lastRow-firstRow)+firstRow;
1256
1257 if (nnzPerRow == pat->columns()) // completely dense
1258 scatterRowRange<0>(first,last,firstRow,rowRangeStart,rowRangeEnd);
1259 else if (nnzPerRow > 10*(first->cidx().size())) // many more entries than we scatter right now
1260 scatterRowRange<1>(first,last,firstRow,rowRangeStart,rowRangeEnd);
1261 else // rather few entries per row
1262 scatterRowRange<2>(first,last,firstRow,rowRangeStart,rowRangeEnd);
1263 }
1264 }
1265
1271 template <class F, class EntryB, class IndexB>
1273 {
1274 auto const& p = *pat;
1275 Index first = p.first(); // first row index
1276 Index last = p.last(); // end row index
1277
1278 for (Index row=first; row<last; ++row)
1279 {
1280 auto const& Br = B[row]; // get the other matrix row
1281 size_t jEnd = p.colStart(row+1-first); // step through all entries in this row
1282 auto bri = Br.begin();
1283 auto brend = Br.end();
1284 for (size_t j=p.colStart(row-first); j<jEnd; ++j)
1285 {
1286 while (bri!= brend && bri.index()<p.col(j)) // advance other row pointer until we find our entry (but do not overshoot)
1287 ++bri;
1288 if (bri!=brend && bri.index()==p.col(j)) // if the column indices coincide, apply the functor
1289 values[j] = f(values[j],*bri);
1290 }
1291 }
1292 }
1293
1294 private:
1295 std::shared_ptr<CRSChunkPattern<Index>> pat;
1296 std::vector<Mutex> scatterMutex;
1297
1298 // Raw data storage. The allocators used are NUMA allocators to guarantee local memory access.
1299 std::vector<Entry,NumaAllocator<Entry>> values; // matrix entries
1300
1301 // scatters given local matrices into the provided row range. The expected sparsity is
1302 // encoded as static template parameter in order to allow an efficient if-statement in
1303 // the innermost loop
1304 template <int sparsity, class LMIterator>
1305 void scatterRowRange(LMIterator first, LMIterator last, Index startRow, Index firstRow, Index lastRow)
1306 {
1307 // simple-minded scatter implementation
1308 for ( ; first!=last; ++first) // step through all local matrices
1309 {
1310 for (auto rgl: first->ridx()) // step through all global rows affected by this matrix
1311 {
1312 Index rg = rgl.first;
1313 if (rg >= lastRow) // global row indices in rgl.first are sorted ascendingly,
1314 break; // if we pass our last row, we can stop with this local matrix
1315 if (firstRow<=rg)
1316 scatterRow<sparsity>(*first,rg-startRow,rgl.second);
1317 }
1318 }
1319 }
1320
1321 template <int sparsity, class LM, class LIndex>
1322 void scatterRow(LM const& a, Index rg, LIndex rl)
1323 {
1324 auto cbegin = pat->colStartIterator(rg);
1325 auto cend = pat->colStartIterator(rg+1);
1326 auto vbegin = valStartIterator(rg);
1327
1328 if (LM::lumped) // only diagonal, i.e. column index is row index both in global and local matrix
1329 {
1330 auto pos = std::find(cbegin,cend,rg+pat->first());
1331 assert(pos != cend);
1332 vbegin[pos-cbegin] += a(rl,rl);
1333 }
1334 else
1335 {
1336 for (auto cgl: a.cidx())
1337 {
1338 auto pos = sparsity==0? std::lower_bound(cbegin,cend,cgl.first) // find entry in row with global column number cgl.first
1339 : sparsity==1? std::lower_bound(cbegin,cend,cgl.first) // TODO: specialized find for dense matrices.
1340 : std::find(cbegin,cend,cgl.first);
1341
1342 if (pos==cend)
1343 return; // reached end of row - skip the rest (there may be more, e.g. if we are symmetric)
1344
1345 vbegin += pos-cbegin; // move on to the found entry
1346 cbegin = pos;
1347
1348 *vbegin += a(rl,cgl.second); // add up appropriate entry from local matrix
1349 }
1350 }
1351 }
1352 };
1353
1354 // ---------------------------------------------------------------------------------------------
1355 }
1356}
1357
1358
1359#endif
std::vector< SparseIndexInt > ridx
row indices
Definition triplet.hh:669
std::vector< SparseIndexInt > cidx
column indices
Definition triplet.hh:671
std::vector< Scalar > data
data
Definition triplet.hh:673
An STL allocator that uses memory of a specific NUMA node only.
Definition threading.hh:666
A NUMA-aware compressed row storage matrix adhering mostly to the Dune ISTL interface (to complete....
iterator end()
returns an iterator to the first row
iterator begin()
returns an iterator to the first row
virtual int size() const
Number of shape functions in the set.
CRSChunk(std::shared_ptr< CRSChunkPattern< Index > > const &pattern_, MatrixAsTriplet< Scalar, Index2 > const &matrix, bool isSymmetric, bool isTransposed)
Constructor.
CRSChunk(int node)
Constructor initializing an empty Chunk.
void scatter(LMIterator first, LMIterator last)
Scatters given sub-matrices into the chunk by adding up their entries.
CRSChunk(std::shared_ptr< CRSChunkPattern< Index > > const &pattern_, Entry const &init)
Constructor.
auto valStartIterator(Index row)
Returns an iterator to the start of the values for the given local row index.
void applyTransposed(Scalar a, Domain const &x, Range &y) const
Transpose matrix-vector multiplication or .
Scalar apply(Scalar a, Domain const &x, Range &y, bool initialize) const
Matrix-vector multiplication or and .
CRSChunk(std::shared_ptr< CRSChunkPattern< Index > > const &pattern_, Matrix const &matrix, bool isSymmetric, bool isTransposed)
Constructor.
CRSChunk(std::shared_ptr< CRSChunkPattern< Index > > const &pattern, CRSChunk< EntryA, IndexA > const &c)
Copy constructor.
Self & operator=(Self const &c)=default
Copy assignment.
void gatherMirrored(Scalar a, Domain const &x, Range &y, CRSChunk< Entry, Index > const &block, bool subdiagonal, bool initToZero) const
Matrix-vector multiplication of transposed parts.
void entrywiseOp(F const &f, NumaBCRSMatrix< EntryB, IndexB > const &B)
Performs entry-wise operations on our own matrix and some other.
CRSChunk(std::shared_ptr< CRSChunkPattern< Index > > const &pattern_, Expanded const &eIndices, Condensed const &cIndices, Matrix const &matrix)
Constructor.
Self & operator=(Value const &a)
Assigns the given value to each entry.
CRSChunk(Matrix const &matrix, bool isSymmetric, bool isTransposed, Index firstRow, Index lastRow, bool symmetric, int node)
Constructor.
Self & operator=(ThreadedMatrixDetail::NumaBCRSMatrixExpressionChunk< Arguments, Operation > const &e)
Assigns the given matrix expression componentwise.
A class supporting two-stage construction of sparsity patterns of NUMA matrix chunks.
size_t nonzeroes() const
Returns the number of stored entries (structurally nonzero elements).
size_t balanceBackward(size_t covered, size_t const nnz, int chunks, std::vector< IndexArray > &moveRows)
Moves rows in and out of the chunk in order to equilibrate the number of nonzeroes.
CRSChunkPatternCreator(Index firstRow, Index lastRow, Index ncols, bool symmetric, int node)
Constructor.
void addAllElements(Index columns)
Enters all possible elements (defining a dense chunk).
void addElements(IterRow fromRow, IterRow const toRow, IterCol const fromCol, IterCol const toCol, bool colIsSorted=false)
Enters elements into the sparsity pattern.
IndexArray const & row(Index i) const
Returns the sorted and unique column indices of elements in row .
void clear()
Clears the complete data, handing back the memory.
size_t balanceForward(size_t const covered, size_t const nnz, int chunks, std::vector< IndexArray > &moveRows)
Moves rows in and out of the chunk in order to equilibrate the number of nonzeroes.
void reserve(Index nnzPerRow=8)
Reserves a certain amount of entries per row.
This class maintains the sparsity structure of a couple of matrix rows (a NUMA matrix chunk).
bool exists(Index r, Index c) const
returns true if the entry with global row and column indices is present in the sparsity pattern
size_t colStart(Index row) const
returns the index from which on the entries in given local row are stored
size_t storage() const
Returns the number of stored entries.
CRSChunkPattern(CRSChunkPatternCreator< Index > const &creator)
Constructor.
CRSChunkPattern(Index first, Index last, Expanded const &eIndices, Condensed const &cIndices, Matrix const &mat, int node)
Constructor.
Index nonzeroesPerRow() const
Returns the average number of nonzeroes per row.
size_t position(Index r, Index c) const
returns the position of an element with given global row and column indices
CRSChunkPattern(CRSChunkPattern< IndexP > const &p)
Copy constructor.
CRSChunkPattern(Matrix const &matrix, bool isSymmetric, bool isTransposed, Index firstRow, Index lastRow, bool symmetric, int node)
Constructor.
CRSChunkPattern(MatrixAsTriplet< Scalar, Index2 > const &matrix, bool isSymmetric, bool isTransposed, Index firstRow, Index lastRow, bool symmetric, int node)
Constructor.
std::vector< Index, NumaAllocator< Index > >::const_iterator colStartIterator(Index row) const
an iterator pointing to the start of the column index for the given local row
Index columns() const
number of columns in the matrix
int node() const
the NUMA node on which to allocate the memory
Index last() const
end of the half-open row range
CRSChunkPatternInfo(CRSChunkPatternInfo< IndexP > const &pi)
Copy constructor.
CRSChunkPatternInfo(Index first_, Index last_, Index cols_, bool symmetric, int node)
Constructor.
Index first() const
start of the covered row range
bool symmetric() const
if true, only the lower triangular part is stored
This file contains various utility functions that augment the basic functionality of Dune.
DynamicMatrix< MEntry > submatrix(DynamicMatrix< MEntry > const &A, int rowstart, int rowend, int colstart, int colend)
extracts contiguous submatrices, copying the data
Index uniformWeightRangeStart(BlockIndex i, BlockIndex n, Index m)
Computes partitioning points of ranges for uniform weight distributions.
Definition threading.hh:78
void getRowCount(Dune::BCRSMatrix< Entry, Allocator > const &matrix, bool isTransposed, std::vector< Index > &rowCount)
T transpose(T x)
Entry field_type
Definition scalar.hh:80
static void init(Index first, Index last, Matrix const &matrix, std::vector< size_t, NumaAllocator< size_t > > &colStart, std::vector< Index, NumaAllocator< Index > > &cols, std::vector< Entry, NumaAllocator< Entry > > &values, std::vector< Index > const &nRowEntries)
static void init(Index first, Index last, Matrix const &matrix, std::vector< size_t, NumaAllocator< size_t > > &colStart, std::vector< Index, NumaAllocator< Index > > &cols, std::vector< Entry, NumaAllocator< Entry > > &values, std::vector< Index > const &nRowEntries)
static void init(Index first, Index last, Matrix const &matrix, std::vector< size_t, NumaAllocator< size_t > > &colStart, std::vector< Index, NumaAllocator< Index > > &cols, std::vector< Entry, NumaAllocator< Entry > > &values, std::vector< Index > const &)
static void copy(Entry const &from, Entry &to, bool isTransposed)
static void copy(From const &from, To &to, bool isTransposed)
static void copy(From const &from, To &to, bool isTransposed)