diff --git a/dune/istl/matrixindexset.hh b/dune/istl/matrixindexset.hh index 10baccf35c6e56b11da8b1ada0c5a8130d8dce63..acc4466b4a093ff0923042c36ef3fe08a01fa2d1 100644 --- a/dune/istl/matrixindexset.hh +++ b/dune/istl/matrixindexset.hh @@ -5,55 +5,139 @@ #ifndef DUNE_ISTL_MATRIXINDEXSET_HH #define DUNE_ISTL_MATRIXINDEXSET_HH -#include <vector> +#include <algorithm> +#include <cstddef> +#include <cstdint> #include <set> +#include <variant> +#include <vector> + +#include <dune/common/overloadset.hh> namespace Dune { - /** \brief Stores the nonzero entries in a sparse matrix */ + /** + * \brief Stores the nonzero entries for creating a sparse matrix + * + * This stores std::set-like container for the column index + * of each row. A sorted std::vector is used for this container + * up to a customizable maxVectorSize. If this size is exceeded, + * storage of the corresponding row is switched to std::set. + * + * The default value for maxVectorSize works well and ensures + * that the slow std::set fallback is only used for very + * dense rows. + */ class MatrixIndexSet { + using Index = std::size_t; + + // A vector that partly mimics a std::set by staying + // sorted on insert() and having unique values. + class FlatSet : public std::vector<Index> + { + using Base = std::vector<Index>; + public: + using Base::Base; + using Base::begin; + using Base::end; + void insert(const Index& value) { + auto it = std::lower_bound(begin(), end(), value); + if ((it == end() or (*it != value))) + Base::insert(it, value); + } + bool contains(const Index& value) const { + return std::binary_search(begin(), end(), value); + } + }; + + using RowIndexSet = std::variant<FlatSet, std::set<Index>>; public: - typedef std::size_t size_type; - /** \brief Default constructor */ - MatrixIndexSet() : rows_(0), cols_(0) + using size_type = Index; + + /** + * \brief Default value for maxVectorSize + * + * This was selected after benchmarking for the worst case + * of reverse insertion of column indices. In many applications + * this works well. There's no need to use a different value + * unless you have many dense rows with more than defaultMaxVectorSize + * nonzero entries. Even in this case defaultMaxVectorSize may work + * well and a finding a better value may require careful + * benchmarking. + */ + static constexpr size_type defaultMaxVectorSize = 2048; + + /** + * \brief Constructor with custom maxVectorSize + * + * \param maxVectorSize Maximal size for stored row vector (default is defaultMaxVectorSize). + */ + MatrixIndexSet(size_type maxVectorSize=defaultMaxVectorSize) noexcept : rows_(0), cols_(0), maxVectorSize_(maxVectorSize) {} - /** \brief Constructor setting the matrix size */ - MatrixIndexSet(size_type rows, size_type cols) : rows_(rows), cols_(cols) { - indices_.resize(rows_); + /** + * \brief Constructor setting the matrix size + * + * \param rows Number of matrix rows + * \param cols Number of matrix columns + * \param maxVectorSize Maximal size for stored row vector (default is defaultMaxVectorSize). + */ + MatrixIndexSet(size_type rows, size_type cols, size_type maxVectorSize=defaultMaxVectorSize) : rows_(rows), cols_(cols), maxVectorSize_(maxVectorSize) + { + indices_.resize(rows_, FlatSet()); } /** \brief Reset the size of an index set */ void resize(size_type rows, size_type cols) { rows_ = rows; cols_ = cols; - indices_.resize(rows_); + indices_.resize(rows_, FlatSet()); } /** \brief Add an index to the index set */ - void add(size_type i, size_type j) { - indices_[i].insert(j); + void add(size_type row, size_type col) { + return std::visit(Dune::overload( + // If row is stored as set, call insert directly + [&](std::set<size_type>& set) { + set.insert(col); + }, + // If row is stored as vector only insert directly + // if maxVectorSize_ is not reached. Otherwise switch + // to set storage first. + [&](FlatSet& sortedVector) { + if (sortedVector.size() < maxVectorSize_) + sortedVector.insert(col); + else if (not sortedVector.contains(col)) + { + std::set<size_type> set(sortedVector.begin(), sortedVector.end()); + set.insert(col); + indices_[row] = std::move(set); + } + } + ), indices_[row]); } /** \brief Return the number of entries */ size_type size() const { size_type entries = 0; for (size_type i=0; i<rows_; i++) - entries += indices_[i].size(); - + entries += rowsize(i); return entries; } /** \brief Return the number of rows */ size_type rows() const {return rows_;} - /** \brief Return the number of entries in a given row */ - size_type rowsize(size_type row) const {return indices_[row].size();} + size_type rowsize(size_type row) const { + return std::visit([&](const auto& rowIndices) { + return rowIndices.size(); + }, indices_[row]); + } /** \brief Import all nonzero entries of a sparse matrix into the index set \tparam MatrixType Needs to be BCRSMatrix<...> @@ -92,17 +176,16 @@ namespace Dune { matrix.setSize(rows_, cols_); matrix.setBuildMode(MatrixType::random); - for (size_type i=0; i<rows_; i++) - matrix.setrowsize(i, indices_[i].size()); + for (size_type row=0; row<rows_; row++) + matrix.setrowsize(row, rowsize(row)); matrix.endrowsizes(); - for (size_type i=0; i<rows_; i++) { - - typename std::set<size_type>::iterator it = indices_[i].begin(); - for (; it!=indices_[i].end(); ++it) - matrix.addindex(i, *it); - + for (size_type row=0; row<rows_; row++) { + std::visit([&](const auto& rowIndices) { + for(size_type col : rowIndices) + matrix.addindex(row, col); + }, indices_[row]); } matrix.endindices(); @@ -111,9 +194,10 @@ namespace Dune { private: - std::vector<std::set<size_type> > indices_; + std::vector<RowIndexSet> indices_; size_type rows_, cols_; + size_type maxVectorSize_; };