#ifndef EIGEN3_HDF5_HPP_ #define EIGEN3_HDF5_HPP_ #include #include #include #include #include #include #include #include #include #if H5_VERSION_LE( 1, 10, 0 ) // typedef H5::H5Location Eigen3Hdf5_H5Location; // typedef H5::CommonFG Eigen3Hdf5_H5CommonFG; #define Eigen3Hdf5_H5Location H5::H5Location #define Eigen3Hdf5_H5CommonFG H5::CommonFG #else // typedef H5::H5Object Eigen3Hdf5_H5Location; // typedef H5::H5Object Eigen3Hdf5_H5CommonFG; #define Eigen3Hdf5_H5Location H5::H5Object #define Eigen3Hdf5_H5CommonFG H5::H5Object #endif namespace Eigen3HDF5 { template struct DatatypeSpecialization; // floating-point types template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_FLOAT ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_DOUBLE ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_LDOUBLE ); } }; // integer types template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_SHORT ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_USHORT ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_INT ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_UINT ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_LONG ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_ULONG ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_LLONG ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { return( &H5::PredType::NATIVE_ULLONG ); } }; // complex types // // inspired by http://www.mail-archive.com/hdf-forum@hdfgroup.org/msg00759.html template class ComplexH5Type : public H5::CompType { public: ComplexH5Type ( void ) : CompType( sizeof( std::complex ) ) { const H5::DataType* const datatype = DatatypeSpecialization::get(); assert( datatype->getSize() == sizeof( T ) ); // If we call the members "r" and "i", h5py interprets the // structure correctly as complex numbers. this->insertMember( std::string( "r" ), 0, *datatype ); this->insertMember( std::string( "i" ), sizeof( T ), *datatype ); this->pack(); } static const ComplexH5Type* get_singleton( void ) { // NOTE: constructing this could be a race condition static ComplexH5Type singleton; return( &singleton ); } }; template struct DatatypeSpecialization > { static inline const H5::DataType* get( void ) { return( ComplexH5Type::get_singleton() ); } }; // string types, to be used mainly for attributes template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { static const H5::StrType strtype( 0, H5T_VARIABLE ); return( &strtype ); } }; template <> struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { static const H5::StrType strtype( 0, H5T_VARIABLE ); return( &strtype ); } }; // XXX: for some unknown reason the following two functions segfault if // H5T_VARIABLE is used. The passed strings should still be null-terminated, // so this is a bit worrisome. template struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { static const H5::StrType strtype( 0, N ); return( &strtype ); } }; template struct DatatypeSpecialization { static inline const H5::DataType* get( void ) { static const H5::StrType strtype( 0, N ); return( &strtype ); } }; namespace internal { template H5::DataSpace create_dataspace( const Eigen::EigenBase& mat ) { const std::size_t dimensions_size = 2; const hsize_t dimensions[dimensions_size] = { static_cast( mat.rows() ), static_cast( mat.cols() ) }; return( H5::DataSpace( dimensions_size, dimensions ) ); } template bool write_rowmat( const Eigen::EigenBase& mat, const H5::DataType* const datatype, H5::DataSet* dataset, const H5::DataSpace* dspace ) { if ( mat.derived().innerStride() != 1 ) { // inner stride != 1 is an edge case this function does not (yet) handle. (I think it // could by using the inner stride as the first element of mstride below. But I do // not have a test case to try it out, so just return false for now.) return( false ); } assert( mat.rows() >= 0 ); assert( mat.cols() >= 0 ); assert( mat.derived().outerStride() >= 0 ); hsize_t rows = hsize_t( mat.rows() ); hsize_t cols = hsize_t( mat.cols() ); hsize_t stride = hsize_t( mat.derived().outerStride() ); // slab params for the file data hsize_t fstride[2] = { 1, cols }; // slab params for the memory data hsize_t mstride[2] = { 1, stride }; // slab params for both file and memory data hsize_t count[2] = { 1, 1 }; hsize_t block[2] = { rows, cols }; hsize_t start[2] = { 0, 0 }; // memory dataspace hsize_t mdim[2] = { rows, stride }; H5::DataSpace mspace( 2, mdim ); dspace->selectHyperslab( H5S_SELECT_SET, count, start, fstride, block ); mspace.selectHyperslab( H5S_SELECT_SET, count, start, mstride, block ); dataset->write( mat.derived().data(), *datatype, mspace, *dspace ); return( true ); } template bool write_colmat( const Eigen::EigenBase& mat, const H5::DataType* const datatype, H5::DataSet* dataset, const H5::DataSpace* dspace ) { if ( mat.derived().innerStride() != 1 ) { // inner stride != 1 is an edge case this function does not (yet) handle. (I think it // could by using the inner stride as the first element of mstride below. But I do // not have a test case to try it out, so just return false for now.) return( false ); } assert( mat.rows() >= 0 ); assert( mat.cols() >= 0 ); assert( mat.derived().outerStride() >= 0 ); hsize_t rows = hsize_t( mat.rows() ); hsize_t cols = hsize_t( mat.cols() ); hsize_t stride = hsize_t( mat.derived().outerStride() ); // slab params for the file data hsize_t fstride[2] = { 1, cols }; hsize_t fcount[2] = { 1, 1 }; hsize_t fblock[2] = { 1, cols }; // slab params for the memory data hsize_t mstride[2] = { stride, 1 }; hsize_t mcount[2] = { 1, 1 }; hsize_t mblock[2] = { cols, 1 }; // memory dataspace hsize_t mdim[2] = { cols, stride }; H5::DataSpace mspace( 2, mdim ); // transpose the column major data in memory to the row major data in the file by // writing one row slab at a time. for ( hsize_t i = 0; i < rows; i++ ) { hsize_t fstart[2] = { i, 0 }; hsize_t mstart[2] = { 0, i }; dspace->selectHyperslab( H5S_SELECT_SET, fcount, fstart, fstride, fblock ); mspace.selectHyperslab( H5S_SELECT_SET, mcount, mstart, mstride, mblock ); dataset->write( mat.derived().data(), *datatype, mspace, *dspace ); } return( true ); } } template void save_scalar_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, const T& value ) { const H5::DataType* const datatype = DatatypeSpecialization::get(); H5::DataSpace dataspace( H5S_SCALAR ); H5::Attribute att = h5obj.createAttribute( name, *datatype, dataspace ); att.write( *datatype, &value ); } template <> inline void save_scalar_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, const std::string& value ) { save_scalar_attribute( h5obj, name, value.c_str() ); } // see http://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html template void save( Eigen3Hdf5_H5CommonFG& h5group, const std::string& name, const Eigen::EigenBase& mat, const H5::DSetCreatPropList& plist = H5::DSetCreatPropList::DEFAULT ) { typedef typename Derived::Scalar Scalar; const H5::DataType* const datatype = DatatypeSpecialization::get(); const H5::DataSpace dataspace = internal::create_dataspace( mat ); H5::DataSet dataset = h5group.createDataSet( name, *datatype, dataspace, plist ); bool written = false; // flag will be true when the data has been written if ( mat.derived().Flags & Eigen::RowMajor ) { written = internal::write_rowmat( mat, datatype, &dataset, &dataspace ); } else { written = internal::write_colmat( mat, datatype, &dataset, &dataspace ); } if ( !written ) { // data has not yet been written, so there is nothing else to try but copy the input // matrix to a row major matrix and write it. const Eigen::Matrix row_major_mat( mat ); dataset.write( row_major_mat.data(), *datatype ); } } template void save_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, const Eigen::EigenBase& mat ) { typedef typename Derived::Scalar Scalar; const Eigen::Matrix row_major_mat( mat ); const H5::DataSpace dataspace = internal::create_dataspace( mat ); const H5::DataType* const datatype = DatatypeSpecialization::get(); H5::Attribute dataset = h5obj.createAttribute( name, *datatype, dataspace ); dataset.write( *datatype, row_major_mat.data() ); } namespace internal { // H5::Attribute and H5::DataSet both have similar API's, and although they // share a common base class, the relevant methods are not virtual. Worst // of all, they take their arguments in different orders! template inline void read_data( const H5::DataSet& dataset, Scalar* data, const H5::DataType& datatype ) { dataset.read( data, datatype ); } template inline void read_data( const H5::Attribute& dataset, Scalar* data, const H5::DataType& datatype ) { dataset.read( datatype, data ); } // read a column major attribute; I do not know if there is an hdf routine to read an // attribute hyperslab, so I take the lazy way out: just read the conventional hdf // row major data and let eigen copy it into mat. template bool read_colmat( const Eigen::DenseBase& mat, const H5::DataType* const datatype, const H5::Attribute& dataset ) { typename Derived::Index rows = mat.rows(); typename Derived::Index cols = mat.cols(); typename Eigen::Matrix temp( rows, cols ); internal::read_data( dataset, temp.data(), *datatype ); const_cast&>( mat ) = temp; return( true ); } template bool read_colmat( const Eigen::DenseBase& mat, const H5::DataType* const datatype, const H5::DataSet& dataset ) { if ( mat.derived().innerStride() != 1 ) { // inner stride != 1 is an edge case this function does not (yet) handle. (I think it // could by using the inner stride as the first element of mstride below. But I do // not have a test case to try it out, so just return false for now.) return( false ); } assert( mat.rows() >= 0 ); assert( mat.cols() >= 0 ); assert( mat.derived().outerStride() >= 0 ); hsize_t rows = hsize_t( mat.rows() ); hsize_t cols = hsize_t( mat.cols() ); hsize_t stride = hsize_t( mat.derived().outerStride() ); if ( stride != rows ) { // this function does not (yet) read into a mat that has a different stride than the // dataset. return( false ); } // slab params for the file data hsize_t fstride[2] = { 1, cols }; hsize_t fcount[2] = { 1, 1 }; hsize_t fblock[2] = { 1, cols }; // file dataspace hsize_t fdim[2] = { rows, cols }; H5::DataSpace fspace( 2, fdim ); // slab params for the memory data hsize_t mstride[2] = { stride, 1 }; hsize_t mcount[2] = { 1, 1 }; hsize_t mblock[2] = { cols, 1 }; // memory dataspace hsize_t mdim[2] = { cols, stride }; H5::DataSpace mspace( 2, mdim ); // transpose the column major data in memory to the row major data in the file by // writing one row slab at a time. for ( hsize_t i = 0; i < rows; i++ ) { hsize_t fstart[2] = { i, 0 }; hsize_t mstart[2] = { 0, i }; fspace.selectHyperslab( H5S_SELECT_SET, fcount, fstart, fstride, fblock ); mspace.selectHyperslab( H5S_SELECT_SET, mcount, mstart, mstride, mblock ); dataset.read( const_cast&>( mat ).derived().data(), *datatype, mspace, fspace ); } return( true ); } template void _load( const DataSet& dataset, const Eigen::DenseBase& mat ) { typedef typename Derived::Scalar Scalar; const H5::DataSpace dataspace = dataset.getSpace(); const std::size_t ndims = dataspace.getSimpleExtentNdims(); assert( ndims > 0 ); const std::size_t dimensions_size = 2; hsize_t dimensions[dimensions_size]; dimensions[1] = 1; // in case it's 1D if ( ndims > dimensions_size ) { throw std::runtime_error( "HDF5 array has too many dimensions." ); } dataspace.getSimpleExtentDims( dimensions ); const hsize_t rows = dimensions[0], cols = dimensions[1]; const H5::DataType* const datatype = DatatypeSpecialization::get(); Eigen::DenseBase& mat_ = const_cast&>( mat ); mat_.derived().resize( rows, cols ); bool written = false; bool isRowMajor = mat.Flags & Eigen::RowMajor; if ( isRowMajor || ( dimensions[0] == 1 ) || ( dimensions[1] == 1 ) ) { // mat is already row major typename Derived::Index istride = mat_.derived().outerStride(); assert( istride >= 0 ); hsize_t stride = istride >= 0 ? istride : 0; if ( ( stride == cols ) || ( ( stride == rows ) && ( cols == 1 ) ) ) { // mat has natural stride, so read directly into its data block read_data( dataset, mat_.derived().data(), *datatype ); written = true; } } else { // colmajor flag is 0 so the assert needs to check that mat is not rowmajor. assert( !( mat.Flags & Eigen::RowMajor ) ); written = read_colmat( mat_, datatype, dataset ); } if ( !written ) { // dataset has not been loaded directly into mat_, so as a last resort read it into a // temp and copy it to mat_. (Should only need to do this when the mat_ to be loaded // into has an unnatural stride.) Eigen::Matrix temp( rows, cols ); internal::read_data( dataset, temp.data(), *datatype ); mat_ = temp; written = true; } } } template void load( const Eigen3Hdf5_H5CommonFG& h5group, const std::string& name, const Eigen::DenseBase& mat ) { const H5::DataSet dataset = h5group.openDataSet( name ); internal::_load( dataset, mat ); } template void load_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, const Eigen::DenseBase& mat ) { const H5::Attribute dataset = h5obj.openAttribute( name ); internal::_load( dataset, mat ); } template void load_scalar_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, T& value ) { const H5::Attribute att = h5obj.openAttribute( name ); const H5::DataType* const datatype = DatatypeSpecialization::get(); att.read( *datatype, &value ); } void load_scalar_attribute( const Eigen3Hdf5_H5Location& h5obj, const std::string& name, std::string& value ) { const H5::Attribute att = h5obj.openAttribute( name ); const H5::DataType* const datatype = DatatypeSpecialization::get(); att.read( *datatype, value ); } } // namespace Eigen3HDF5 #endif