diff --git a/dune/istl/matrixmarket.hh b/dune/istl/matrixmarket.hh new file mode 100644 index 0000000000000000000000000000000000000000..2648fffceef3ded107a5c87c4d984592efc5a7f2 --- /dev/null +++ b/dune/istl/matrixmarket.hh @@ -0,0 +1,661 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +#ifndef DUNE_MATRIXMARKET_HH +#define DUNE_MATRIXMARKET_HH + +#include <ostream> +#include "bcrsmatrix.hh" +#include <dune/common/fmatrix.hh> +namespace Dune +{ + namespace + { + /** + * @brief Helper metaprogram to get + * the matrix market string representation + * of the numeric type. + * + * Member function mm_numeric_type::c_str() + * returns the string representation of + * the type. + */ + template<class T> + struct mm_numeric_type; + + template<> + struct mm_numeric_type<int> + { + static std::string str() + { + return "integer"; + } + }; + + template<> + struct mm_numeric_type<double> + { + static std::string str() + { + return "real"; + } + }; + + template<> + struct mm_numeric_type<float> + { + static std::string str() + { + return "real"; + } + }; + + template<> + struct mm_numeric_type<std::complex<double> > + { + static std::string str() + { + return "complex"; + } + }; + + template<> + struct mm_numeric_type<std::complex<float> > + { + static std::string str() + { + return "complex"; + } + }; + + /** + * @brief Meta program to write the correct Matrix Market + * header. + * + * Member function mm_header::operator() writes the header. + * + * @tparam M The matrix type. + */ + template<class M> + struct mm_header_printer; + + template<typename T, typename A, size_t i, size_t j> + struct mm_header_printer<BCRSMatrix<FieldMatrix<T,i,j>,A> > + { + static void print(std::ostream& os) + { + os<<"%%MatrixMarket matrix coordinate "; + os<<mm_numeric_type<T>::str()<<" general"<<std::endl; + } + }; + + template<typename T, size_t i, size_t j> + struct mm_header_printer<FieldMatrix<T,i,j> > + { + static void print(std::ostream& os) + { + os<<"%%MatrixMarket matrix array "; + os<<mm_numeric_type<T>::str()<<" general"<<std::endl; + } + }; + + /** + * @brief Metaprogram for writing the ISTL block + * structure header. + * + * Member function mm_block_structure_header::operaor() writes + * the corresponding header to the specified ostream. + * @tparam The type of the matrix to generate the header for. + */ + template<class M> + struct mm_block_structure_header; + + template<typename T, typename A, size_t i, size_t j> + struct mm_block_structure_header<BCRSMatrix<FieldMatrix<T,i,j>,A> > + { + typedef BCRSMatrix<FieldMatrix<T,i,j>,A> M; + + static void print(std::ostream& os, const M& m) + { + os<<"% ISTL_STRUCT blocked "; + os<<i<<" "<<j<<std::endl; + } + }; + + template<typename T, size_t i, size_t j> + struct mm_block_structure_header<FieldMatrix<T,i,j> > + { + typedef FieldMatrix<T,i,j> M; + + static void print(std::ostream& os, const M& m) + {} + }; + + + enum LineType { MM_HEADER, MM_ISTLSTRUCT, DATA }; + enum { MM_MAX_LINE_LENGTH=1025 }; + + enum MM_TYPE { coordinate_type, array_type, unknown_type }; + + enum MM_CTYPE { integer_type, double_type, complex_type, pattern, unknown_ctype }; + + enum MM_STRUCTURE { general, symmetric, skew_symmetric, hermitian, unknown_structure }; + + struct MMHeader + { + MMHeader() + : type(coordinate_type), ctype(double_type), structure(general) + {} + MM_TYPE type; + MM_CTYPE ctype; + MM_STRUCTURE structure; + }; + + bool lineFeed(std::istream& file) + { + char c=file.peek(); + // ignore whitespace + while(c==' ') + { + file.get(); + c=file.peek(); + } + + if(c=='\n') { + /* eat the line feed */ + file.get(); + return true; + } + return false; + } + + void skipComments(std::istream& file) + { + lineFeed(file); + char c=file.peek(); + // ignore comment lines + while(c=='%') + { + /* disgard the rest of the line */ + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + c=file.peek(); + } + } + + + bool readMatrixMarketBanner(std::istream& file, MMHeader& mmHeader) + { + std::string buffer; + char c; + c=file.peek(); + mmHeader=MMHeader(); + if(c!='%') + return false; + + /* read the banner */ + file >> buffer; + if(buffer!="%%MatrixMarket") { + /* disgard the rest of the line */ + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + + if(lineFeed(file)) + /* premature end of line */ + return false; + + /* read the matrix_type */ + file >> buffer; + + if(buffer != "matrix") + { + /* disgard the rest of the line */ + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + + if(lineFeed(file)) + /* premature end of line */ + return false; + + /* The type of the matrix */ + file >> buffer; + + if(buffer.empty()) + return false; + + std::transform(buffer.begin(), buffer.end(), buffer.begin(), + tolower); + + switch(buffer[0]) + { + case 'a' : + /* sanity check */ + if(buffer != "array") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.type=array_type; + break; + case 'c' : + /* sanity check */ + if(buffer != "coordinate") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.type=coordinate_type; + break; + default : + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + + if(lineFeed(file)) + /* premature end of line */ + return false; + + /* The numeric type used. */ + file >> buffer; + + if(buffer.empty()) + return false; + + std::transform(buffer.begin(), buffer.end(), buffer.begin(), + tolower); + switch(buffer[0]) + { + case 'i' : + /* sanity check */ + if(buffer != "integer") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.ctype=integer_type; + break; + case 'r' : + /* sanity check */ + if(buffer != "real") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.ctype=double_type; + break; + case 'c' : + /* sanity check */ + if(buffer != "complex") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.ctype=complex_type; + break; + case 'p' : + /* sanity check */ + if(buffer != "pattern") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.ctype=pattern; + break; + default : + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + + if(lineFeed(file)) + return false; + + file >> buffer; + + std::transform(buffer.begin(), buffer.end(), buffer.begin(), + tolower); + switch(buffer[0]) + { + case 'g' : + /* sanity check */ + if(buffer != "general") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.structure=general; + break; + case 'h' : + /* sanity check */ + if(buffer != "hermitian") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.structure=hermitian; + break; + case 's' : + if(buffer.size()==1) { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + + switch(buffer[1]) + { + case 'y' : + /* sanity check */ + if(buffer != "symmetric") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.structure=symmetric; + break; + case 'k' : + /* sanity check */ + if(buffer != "skew-symmetric") + { + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + mmHeader.structure=skew_symmetric; + break; + default : + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + default : + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + return false; + } + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + c=file.peek(); + return true; + + } + + void readNextLine(std::istream& file, std::ostringstream& line, LineType& type) + { + char c; + std::size_t index=0; + + //empty lines will be disgarded and we will simply read the next line + while(index==0&&!file.eof()) + { + // strip spaces + while(!file.eof() && (c=file.get())==' ') ; + + //read the rest of the line until comment + while(!file.eof() && (c=file.get())=='\n') { + switch(c) + { + case '%' : + /* disgard the rest of the line */ + file.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + } + } + } + + // buffer[index]='\0'; + } + + template<std::size_t brows, std::size_t bcols> + Dune::tuple<std::size_t, std::size_t, std::size_t> + calculateNNZ(std::size_t rows, std::size_t cols, std::size_t entries, const MMHeader& header) + { + std::size_t blockrows=rows/brows; + std::size_t blockcols=cols/bcols; + std::size_t blocksize=brows*bcols; + std::size_t blockentries=0; + + switch(header.structure) + { + case general : + blockentries = entries/blocksize; break; + case skew_symmetric : + blockentries = 2*entries/blocksize; break; + case symmetric : + blockentries = (2*entries-rows)/blocksize; break; + case hermitian : + blockentries = (2*entries-rows)/blocksize; break; + default : + throw Dune::NotImplemented(); + } + return Dune::make_tuple(blockrows, blockcols, blockentries); + } + + /* + * @brief Storage class for the column index and the numeric value. + * + * \tparam T Either a NumericWrapper of the numeric type or PatternDummy + * for MatrixMarket pattern case. + */ + template<typename T> + struct IndexData : public T + { + std::size_t index; + }; + + + /** + * @brief a wrapper class of numeric values. + * + * Use for template specialization to catch the pattern + * type MatrixMarket matrices. Uses Empty base class optimization + * in the pattern case. + * + * @tparam T Either a NumericWrapper of the numeric type or PatternDummy + * for MatrixMarket pattern case. + */ + template<typename T> + struct NumericWrapper + { + T number; + operator T&() + { + return number; + } + }; + + /** + * @brief Utility class for marking the pattern type of the MatrixMarket matrices. + */ + struct PatternDummy + {}; + + template<> + struct NumericWrapper<PatternDummy> + {}; + + template<typename T> + std::istream& operator>>(std::istream& is, NumericWrapper<T>& num) + { + return is>>num.number; + } + + std::istream& operator>>(std::istream& is, NumericWrapper<PatternDummy>& num) + { + return is; + } + + /** + * @brief LessThan operator. + * + * It simply compares the index. + */ + template<typename T> + bool operator<(const IndexData<T>& i1, const IndexData<T>& i2) + { + return i1.index<i2.index; + }; + + /** + * @brief Read IndexData from a stream. + * @param is The input stream we read. + * @param data Where to store the read data. + */ + template<typename T> + std::istream& operator>>(std::istream& is, IndexData<T>& data) + { + is>>data.index; + /* MatrixMarket indices are one based. Decrement for C++ */ + --data.index; + return is>>data.number; + } + + /** + * @brief Functor to the data values of the matrix. + * + * This is specialized for PatternDummy. The specialization does not + * set anything. + */ + template<typename D, int brows, int bcols> + struct MatrixValuesSetter + { + /** + * @brief Sets the matrixvalues. + * @param row The row data as read from file. + * @param matrix The matrix whose data we set. + */ + template<typename M> + void operator()(const std::vector<std::set<IndexData<D> > >& rows, + M& matrix) + { + for(typename M::RowIterator iter=matrix.begin(); + iter!= matrix.end(); ++iter) + { + for(typename M::size_type brow=iter.index()*brows, + browend=iter.index()*brows+brows; + brow<browend; ++brow) + { + typedef typename std::set<IndexData<D> >::const_iterator Siter; + for(Siter siter=rows[brow].begin(), send=rows[brow].end(); + siter != send; ++siter) + (*iter)[siter->index/bcols][brow%brows][siter->index%bcols]=siter->number; + } + } + } + }; + + template<int brows, int bcols> + struct MatrixValuesSetter<PatternDummy,brows,bcols> + { + template<typename M> + void operator()(const std::vector<std::set<IndexData<PatternDummy> > >& rows, + M& matrix) + {} + }; + + template<typename T, typename A, int brows, int bcols, typename D> + void readSparseEntries(Dune::BCRSMatrix<Dune::FieldMatrix<T,brows,bcols>,A>& matrix, + std::istream& file, std::size_t entries, + const MMHeader& mmHeader, D) + { + typedef Dune::BCRSMatrix<Dune::FieldMatrix<T,brows,bcols>,A> Matrix; + std::vector<std::set<IndexData<D> > > rows(matrix.N()*brows); + + for(entries; entries>0; --entries) { + std::size_t row; + IndexData<D> data; + skipComments(file); + file>>row; + --row; // Index was 1 based. + assert(row/bcols<matrix.N()); + file>>data; + assert(data.index/bcols<matrix.M()); + rows[row].insert(data); + } + + // TODO extend to capture the nongeneral cases. + if(mmHeader.structure!= general) + DUNE_THROW(Dune::NotImplemented, "Only general is supported right now!"); + + // Setup the matrix sparsity pattern + int nnz=0; + for(typename Matrix::CreateIterator iter=matrix.createbegin(); + iter!= matrix.createend(); ++iter) + { + for(std::size_t brow=iter.index()*brows, browend=iter.index()*brows+brows; + brow<browend; ++brow) + { + typedef typename std::set<IndexData<D> >::const_iterator Siter; + for(Siter siter=rows[brow].begin(), send=rows[brow].end(); + siter != send; ++siter, ++nnz) + iter.insert(siter->index/bcols); + } + } + + //Set the matrix values + matrix=0; + + MatrixValuesSetter<D,brows,bcols> Setter; + + Setter(rows, matrix); + } + } // end anonymous namespace + + class MatrixMarketFormatError : public Dune::Exception + {}; + + /** + * @brief Reads a sparse matrix from a matrix market file. + * @param matrix The matrix to store the data in. + * @param istr The input stream to read the data from. + * @warning Not all formats are supported! + */ + template<typename T, typename A, int brows, int bcols> + void readMatrixMarket(Dune::BCRSMatrix<Dune::FieldMatrix<T,brows,bcols>,A>& matrix, + std::istream& istr) + { + + typedef Dune::BCRSMatrix<Dune::FieldMatrix<double,brows,bcols> > Matrix; + + MMHeader header; + if(!readMatrixMarketBanner(istr, header)) + std::cerr << "First line was not a correct Matrix Market banner. Using default:\n" + << "%%MatrixMarket matrix coordinate real general"<<std::endl; + + skipComments(istr); + + std::size_t rows, cols, entries; + + if(lineFeed(istr)) + throw MatrixMarketFormatError(); + + istr >> rows; + + if(lineFeed(istr)) + throw MatrixMarketFormatError(); + istr >> cols; + + if(lineFeed(istr)) + throw MatrixMarketFormatError(); + + istr >>entries; + + std::size_t nnz, blockrows, blockcols; + + Dune::tie(blockrows, blockcols, nnz) = calculateNNZ<brows, bcols>(rows, cols, entries, header); + + istr.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); + + + matrix.setSize(blockrows, blockcols); + matrix.setBuildMode(Dune::BCRSMatrix<Dune::FieldMatrix<T,brows,bcols>,A>::row_wise); + + if(header.type==array_type) + DUNE_THROW(Dune::NotImplemented, "currently not supported!"); + + NumericWrapper<double> d; + + readSparseEntries(matrix, istr, entries, header,d); + } + template<typename M> + void printMatrixMarket(M& matrix, + std::ostream ostr) + { + mm_header_printer<M>::print(ostr, matrix); + mm_block_structure_header<M>::print(ostr,matrix); + } + +} +#endif