/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: ./lib/serialisation/BaseIO.h Copyright (C) 2015 Author: Antonin Portelli Author: Peter Boyle Author: Guido Cossu Author: Michael Marshall This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. See the full license in the file "LICENSE" in the top level distribution directory *************************************************************************************/ /* END LEGAL */ #ifndef GRID_SERIALISATION_ABSTRACT_READER_H #define GRID_SERIALISATION_ABSTRACT_READER_H #include #include #include #include #include namespace Grid { namespace EigenIO { // EigenIO works for scalars that are not just Grid supported scalars template struct is_complex : public std::false_type {}; // Support all complex types (not just Grid complex types) - even if the definitions overlap (!) template struct is_complex< T , typename std::enable_if< ::Grid::is_complex< T >::value>::type> : public std::true_type {}; template struct is_complex, typename std::enable_if>::value>::type> : public std::true_type {}; // Helpers to support I/O for Eigen tensors of arithmetic scalars, complex types, or Grid tensors template struct is_scalar : public std::false_type {}; template struct is_scalar::value || is_complex::value>::type> : public std::true_type {}; // Is this an Eigen tensor template struct is_tensor : std::integral_constant, T>::value> {}; // Is this an Eigen tensor of a supported scalar template struct is_tensor_of_scalar : public std::false_type {}; template struct is_tensor_of_scalar::value && is_scalar::value>::type> : public std::true_type {}; // Is this an Eigen tensor of a supported container template struct is_tensor_of_container : public std::false_type {}; template struct is_tensor_of_container::value && isGridTensor::value>::type> : public std::true_type {}; // These traits describe the scalars inside Eigen tensors // I wish I could define these in reference to the scalar type (so there would be fewer traits defined) // but I'm unable to find a syntax to make this work template struct Traits {}; // Traits are the default for scalars, or come from GridTypeMapper for GridTensors template struct Traits::value>::type> : public GridTypeMapper_Base { using scalar_type = typename T::Scalar; // ultimate base scalar static constexpr bool is_complex = ::Grid::EigenIO::is_complex::value; }; // Traits are the default for scalars, or come from GridTypeMapper for GridTensors template struct Traits::value>::type> { using BaseTraits = GridTypeMapper; using scalar_type = typename BaseTraits::scalar_type; // ultimate base scalar static constexpr bool is_complex = ::Grid::EigenIO::is_complex::value; static constexpr int TensorLevel = BaseTraits::TensorLevel; static constexpr int Rank = BaseTraits::Rank; static constexpr std::size_t count = BaseTraits::count; static constexpr int Dimension(int dim) { return BaseTraits::Dimension(dim); } }; // Is this a fixed-size Eigen tensor template struct is_tensor_fixed : public std::false_type {}; template struct is_tensor_fixed> : public std::true_type {}; template struct is_tensor_fixed> : public std::true_type {}; // Is this a variable-size Eigen tensor template struct is_tensor_variable : public std::false_type {}; template struct is_tensor_variable::value && !is_tensor_fixed::value>::type> : public std::true_type {}; // Helper functions to get the ultimate scalar inside a tensor, and corresponding size template inline typename std::enable_if::value, const typename ET::Index>::type getScalarCount(const ET &eigenTensor) { return eigenTensor.size() * Traits::count; } template inline typename std::enable_if::value, const typename ET::Scalar *>::type getFirstScalar(const ET &eigenTensor) { return eigenTensor.data(); } template inline typename std::enable_if::value, typename ET::Scalar *>::type getFirstScalar(ET &eigenTensor) { return eigenTensor.data(); } template inline typename std::enable_if::value, const typename Traits::scalar_type *>::type getFirstScalar(const ET &eigenTensor) { return eigenTensor.data()->begin(); } template inline typename std::enable_if::value, typename Traits::scalar_type *>::type getFirstScalar(ET &eigenTensor) { return eigenTensor.data()->begin(); } // Counter for resized EigenTensors (poor man's substitute for allocator) // Defined in BinaryIO.cc extern std::uint64_t EigenResizeCounter; } // Abstract writer/reader classes //////////////////////////////////////////// // static polymorphism implemented using CRTP idiom class Serializable; // Static abstract writer template class Writer { public: Writer(void); virtual ~Writer(void) = default; void push(const std::string &s); void pop(void); template typename std::enable_if::value>::type write(const std::string& s, const U &output); template typename std::enable_if::value && !EigenIO::is_tensor::value>::type write(const std::string& s, const U &output); template void write(const std::string &s, const iScalar &output); template void write(const std::string &s, const iVector &output); template void write(const std::string &s, const iMatrix &output); template typename std::enable_if::value>::type write(const std::string &s, const ETensor &output); template inline typename std::enable_if::value, void>::type copyScalars(S * &pCopy, const S &Source) { * pCopy ++ = Source; } template inline typename std::enable_if::value, void>::type copyScalars(typename GridTypeMapper::scalar_type * &pCopy, const S &Source) { for( const typename GridTypeMapper::scalar_type &item : Source ) * pCopy ++ = item; } void scientificFormat(const bool set); bool isScientific(void); void setPrecision(const unsigned int prec); unsigned int getPrecision(void); private: T *upcast; bool scientific_{false}; unsigned int prec_{0}; }; // Static abstract reader template class Reader { public: Reader(void); virtual ~Reader(void) = default; bool push(const std::string &s); void pop(void); template typename std::enable_if::value, void>::type read(const std::string& s, U &output); template typename std::enable_if::value && !EigenIO::is_tensor::value, void>::type read(const std::string& s, U &output); template void read(const std::string &s, iScalar &output); template void read(const std::string &s, iVector &output); template void read(const std::string &s, iMatrix &output); template typename std::enable_if::value, void>::type read(const std::string &s, ETensor &output); template typename std::enable_if::value, void>::type Reshape(ETensor &t, const std::array &dims ); template typename std::enable_if::value, void>::type Reshape(ETensor &t, const std::array &dims ); // Helper functions for Scalar vs Container specialisations template inline typename std::enable_if::value, void>::type copyScalars(S &Dest, const S * &pSource) { Dest = * pSource ++; } template inline typename std::enable_if::value, void>::type copyScalars(S &Dest, const typename GridTypeMapper::scalar_type * &pSource) { for( typename GridTypeMapper::scalar_type &item : Dest ) item = * pSource ++; } protected: template void fromString(U &output, const std::string &s); private: T *upcast; }; // What is the vtype template struct isReader { static const bool value = false; }; template struct isWriter { static const bool value = false; }; // Writer template implementation template Writer::Writer(void) { upcast = static_cast(this); } template void Writer::push(const std::string &s) { upcast->push(s); } template void Writer::pop(void) { upcast->pop(); } template template typename std::enable_if::value, void>::type Writer::write(const std::string &s, const U &output) { U::write(*this, s, output); } template template typename std::enable_if::value && !EigenIO::is_tensor::value, void>::type Writer::write(const std::string &s, const U &output) { upcast->writeDefault(s, output); } template template void Writer::write(const std::string &s, const iScalar &output) { upcast->writeDefault(s, tensorToVec(output)); } template template void Writer::write(const std::string &s, const iVector &output) { upcast->writeDefault(s, tensorToVec(output)); } template template void Writer::write(const std::string &s, const iMatrix &output) { upcast->writeDefault(s, tensorToVec(output)); } // Eigen::Tensors of Grid tensors (iScalar, iVector, iMatrix) template template typename std::enable_if::value, void>::type Writer::write(const std::string &s, const ETensor &output) { using Index = typename ETensor::Index; using Container = typename ETensor::Scalar; // NB: could be same as scalar using Traits = EigenIO::Traits; using Scalar = typename Traits::scalar_type; // type of the underlying scalar constexpr unsigned int TensorRank{ETensor::NumIndices}; constexpr unsigned int ContainerRank{Traits::Rank}; // Only non-zero for containers constexpr unsigned int TotalRank{TensorRank + ContainerRank}; const Index NumElements{output.size()}; assert( NumElements > 0 ); // Get the dimensionality of the tensor std::vector TotalDims(TotalRank); for(auto i = 0; i < TensorRank; i++ ) { auto dim = output.dimension(i); TotalDims[i] = static_cast(dim); assert( TotalDims[i] == dim ); // check we didn't lose anything in the conversion } for(auto i = 0; i < ContainerRank; i++ ) TotalDims[TensorRank + i] = Traits::Dimension(i); // If the Tensor isn't in Row-Major order, then we'll need to copy it's data const bool CopyData{NumElements > 1 && static_cast( ETensor::Layout ) != static_cast( Eigen::StorageOptions::RowMajor )}; const Scalar * pWriteBuffer; std::vector CopyBuffer; const Index TotalNumElements = NumElements * Traits::count; if( !CopyData ) { pWriteBuffer = EigenIO::getFirstScalar( output ); } else { // Regardless of the Eigen::Tensor storage order, the copy will be Row Major CopyBuffer.resize( TotalNumElements ); Scalar * pCopy = &CopyBuffer[0]; pWriteBuffer = pCopy; std::array MyIndex; for( auto &idx : MyIndex ) idx = 0; for( auto n = 0; n < NumElements; n++ ) { const Container & c = output( MyIndex ); copyScalars( pCopy, c ); // Now increment the index for( int i = output.NumDimensions - 1; i >= 0 && ++MyIndex[i] == output.dimension(i); i-- ) MyIndex[i] = 0; } } upcast->template writeMultiDim(s, TotalDims, pWriteBuffer, TotalNumElements); } template void Writer::scientificFormat(const bool set) { scientific_ = set; } template bool Writer::isScientific(void) { return scientific_; } template void Writer::setPrecision(const unsigned int prec) { prec_ = prec; } template unsigned int Writer::getPrecision(void) { return prec_; } // Reader template implementation template Reader::Reader(void) { upcast = static_cast(this); } template bool Reader::push(const std::string &s) { return upcast->push(s); } template void Reader::pop(void) { upcast->pop(); } template template typename std::enable_if::value, void>::type Reader::read(const std::string &s, U &output) { U::read(*this, s, output); } template template typename std::enable_if::value && !EigenIO::is_tensor::value, void>::type Reader::read(const std::string &s, U &output) { upcast->readDefault(s, output); } template template void Reader::read(const std::string &s, iScalar &output) { typename TensorToVec>::type v; upcast->readDefault(s, v); vecToTensor(output, v); } template template void Reader::read(const std::string &s, iVector &output) { typename TensorToVec>::type v; upcast->readDefault(s, v); vecToTensor(output, v); } template template void Reader::read(const std::string &s, iMatrix &output) { typename TensorToVec>::type v; upcast->readDefault(s, v); vecToTensor(output, v); } template template typename std::enable_if::value, void>::type Reader::read(const std::string &s, ETensor &output) { using Index = typename ETensor::Index; using Container = typename ETensor::Scalar; // NB: could be same as scalar using Traits = EigenIO::Traits; using Scalar = typename Traits::scalar_type; // type of the underlying scalar constexpr unsigned int TensorRank{ETensor::NumIndices}; constexpr unsigned int ContainerRank{Traits::Rank}; // Only non-zero for containers constexpr unsigned int TotalRank{TensorRank + ContainerRank}; using ETDims = std::array; // Dimensions of the tensor // read the (flat) data and dimensionality std::vector dimData; std::vector buf; upcast->readMultiDim( s, buf, dimData ); assert(dimData.size() == TotalRank && "EigenIO: Tensor rank mismatch" ); // Make sure that the number of elements read matches dimensions read std::size_t NumContainers = 1; for( auto i = 0 ; i < TensorRank ; i++ ) NumContainers *= dimData[i]; // If our scalar object is a Container, make sure it's dimensions match what we read back std::size_t ElementsPerContainer = 1; for( auto i = 0 ; i < ContainerRank ; i++ ) { assert( dimData[TensorRank+i] == Traits::Dimension(i) && "Tensor Container dimensions don't match data" ); ElementsPerContainer *= dimData[TensorRank+i]; } assert( NumContainers * ElementsPerContainer == buf.size() && "EigenIO: Number of elements != product of dimensions" ); // Now see whether the tensor is the right shape, or can be made to be const auto & dims = output.dimensions(); bool bShapeOK = (output.data() != nullptr); for( auto i = 0; bShapeOK && i < TensorRank ; i++ ) if( dims[i] != dimData[i] ) bShapeOK = false; // Make the tensor the same size as the data read ETDims MyIndex; if( !bShapeOK ) { for( auto i = 0 ; i < TensorRank ; i++ ) MyIndex[i] = dimData[i]; Reshape(output, MyIndex); } // Copy the data into the tensor for( auto &d : MyIndex ) d = 0; const Scalar * pSource = &buf[0]; for( std::size_t n = 0 ; n < NumContainers ; n++ ) { Container & c = output( MyIndex ); copyScalars( c, pSource ); // Now increment the index for( int i = TensorRank - 1; i != -1 && ++MyIndex[i] == dims[i]; i-- ) MyIndex[i] = 0; } assert( pSource == &buf[NumContainers * ElementsPerContainer] ); } template template typename std::enable_if::value, void>::type Reader::Reshape(ETensor &t, const std::array &dims ) { assert( 0 && "EigenIO: Fixed tensor dimensions can't be changed" ); } template template typename std::enable_if::value, void>::type Reader::Reshape(ETensor &t, const std::array &dims ) { #ifdef GRID_OMP // The memory counter is the reason this must be done from the primary thread assert(omp_in_parallel()==0 && "Deserialisation which resizes Eigen tensor must happen from primary thread"); #endif EigenIO::EigenResizeCounter -= static_cast(t.size()) * sizeof(typename ETensor::Scalar); //t.reshape( dims ); t.resize( dims ); EigenIO::EigenResizeCounter += static_cast(t.size()) * sizeof(typename ETensor::Scalar); } template template void Reader::fromString(U &output, const std::string &s) { std::istringstream is(s); is.exceptions(std::ios::failbit); try { is >> std::boolalpha >> output; } catch(std::ios_base::failure &e) { std::cerr << "numerical conversion failure on '" << s << "' "; std::cerr << "(typeid: " << typeid(U).name() << ")" << std::endl; abort(); } } // serializable base class /////////////////////////////////////////////////// class Serializable { public: template static inline void write(Writer &WR,const std::string &s, const Serializable &obj) {} template static inline void read(Reader &RD,const std::string &s, Serializable &obj) {} friend inline std::ostream & operator<<(std::ostream &os, const Serializable &obj) { return os; } template static inline typename std::enable_if::value || !EigenIO::is_tensor::value, bool>::type CompareMember(const T1 &lhs, const T2 &rhs) { return lhs == rhs; } template static inline typename std::enable_if::value && EigenIO::is_tensor::value, bool>::type CompareMember(const T1 &lhs, const T2 &rhs) { // First check whether dimensions match (Eigen tensor library will assert if they don't match) bool bReturnValue = (T1::NumIndices == T2::NumIndices); for( auto i = 0 ; bReturnValue && i < T1::NumIndices ; i++ ) bReturnValue = ( lhs.dimension(i) == rhs.dimension(i) ); if( bReturnValue ) { Eigen::Tensor bResult = (lhs == rhs).all(); bReturnValue = bResult(0); } return bReturnValue; } template static inline typename std::enable_if::value, bool>::type CompareMember(const std::vector &lhs, const std::vector &rhs) { const auto NumElements = lhs.size(); bool bResult = ( NumElements == rhs.size() ); for( auto i = 0 ; i < NumElements && bResult ; i++ ) bResult = CompareMember(lhs[i], rhs[i]); return bResult; } template static inline typename std::enable_if::value, void>::type WriteMember(std::ostream &os, const T &object) { os << object; } template static inline typename std::enable_if::value, void>::type WriteMember(std::ostream &os, const T &object) { using Index = typename T::Index; const Index NumElements{object.size()}; assert( NumElements > 0 ); Index count = 1; os << "T<"; for( int i = 0; i < T::NumIndices; i++ ) { Index dim = object.dimension(i); count *= dim; if( i ) os << ","; os << dim; } assert( count == NumElements && "Number of elements doesn't match tensor dimensions" ); os << ">{"; const typename T::Scalar * p = object.data(); for( Index i = 0; i < count; i++ ) { if( i ) os << ","; os << *p++; } os << "}"; } }; // Generic writer interface ////////////////////////////////////////////////// template inline void push(Writer &w, const std::string &s) { w.push(s); } template inline void push(Writer &w, const char *s) { w.push(std::string(s)); } template inline void pop(Writer &w) { w.pop(); } template inline void write(Writer &w, const std::string& s, const U &output) { w.write(s, output); } // Generic reader interface ////////////////////////////////////////////////// template inline bool push(Reader &r, const std::string &s) { return r.push(s); } template inline bool push(Reader &r, const char *s) { return r.push(std::string(s)); } template inline void pop(Reader &r) { r.pop(); } template inline void read(Reader &r, const std::string &s, U &output) { r.read(s, output); } } #endif