/*! * \file * Read Object Data * * This file contains the main mex code for reading object data into MATLAB memory. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "precision.h" #include "hdf5_helper.h" #include "mex.h" #include "read_object_data.h" #include "debug_helper.h" #include "multi_processing.h" namespace { constexpr int min_dims = 2; //!< Minimum number of dimensions (row, col) constexpr int max_dims = 4; //!< Maximum number of dimensions (nslices, nmodes, row, col) #include "mex_helper.h" /*! * \brief Read error exception class */ struct read_error : public std::exception { bool converted = false; //!< has the error message been converted (to include 1-based file index) uint32_t file_index; //!< file index (starting from 0) std::string error_message; //!< error message (converted if converted is true) /*! * \brief Constructor * \param index 0-based file index * \param message unconverted error message */ read_error(uint32_t index, const std::string &message) : file_index(index), error_message(message) {} /*! * \brief Retrieve converted error message * This method first attempts to convert the error message by appending the 1-based file index (MATLAB indexing convention). * If this fails, the unconverted error message is returned. * \return converted error message */ const char* what() const noexcept override { try { if (! converted) { std::ostringstream oss; oss << error_message << " (file " << (file_index+1) << ")"; const_cast(error_message) = oss.str(); const_cast(converted) = true; } } catch (...) {} return error_message.c_str(); } }; /*! * \brief Error collector class * The class either keeps a vector to collect file indices for files that could not be read, or it throws an exception, depending on the in_collect_mode flag. * The class provides a pointer to the singleton instance. */ struct error_collector { static error_collector *singleton; //!< static pointer to make the singleton instance available bool in_collect_mode = false; //!< is the error method collecting or throwing errors? std::vector bad_index; //!< vector of bad file indices /*! * \brief Constructor * \param collect_mode true if errors should be collected instead of thrown */ explicit error_collector(bool collect_mode) : in_collect_mode(collect_mode) { if (! singleton) singleton = this; else throw std::runtime_error("internal error - multiple error_collector instances!"); } /*! * \brief Destructor * Zero out the singleton pointer */ ~error_collector() noexcept { singleton = nullptr; } /*! * \brief Get pointer to singleton instance */ static error_collector& get() noexcept { return *singleton; } /*! * \brief Collect or throw error * \param ex read error exception that would be thrown if not in collector mode */ void error(read_error &&ex) { if (! in_collect_mode) throw ex; bad_index.push_back(ex.file_index); DEBUG { OUT << "error reading file " << ex.file_index << ": " << ex.error_message << std::endl; } } /*! * \brief Add index to bad indices vector * \param index index of file that can not be read */ void add(uint32_t index) noexcept { bad_index.push_back(index); } }; error_collector* error_collector::singleton = nullptr; //!< static error_collectr singleton pointer /*! * \brief Adapt data space layout * * Adapt the memory and file dataset layouts by selecting apropriate hyperslabs * * \param mspace memory data space layout * \param fspace file dataset layout * \param mdim memory space dimensions * \param fdim file dataset dimensions * \param doffset Start of the dimsensions present in the file data */ void adapt_spaces(hdf5::dataspace &mspace, hdf5::dataspace &fspace, const hsize_t mdim[max_dims], const hsize_t fdim[max_dims], int doffset) { hsize_t mstart[max_dims] = {0}; hsize_t fstart[max_dims] = {0}; hsize_t mcount[max_dims] = { mdim[0], mdim[1], mdim[2], mdim[3] }; hsize_t fcount[max_dims] = { fdim[0], fdim[1], fdim[2], fdim[3] }; bool mselect = false; bool fselect = false; for (int i=2; i<=3; i++) { if (mdim[i] < fdim[i]) { fstart[i] = (fdim[i] - mdim[i]) / 2; fcount[i] = mdim[i]; fselect = true; } else if (mdim[i] > fdim[i]) { mstart[i] = (mdim[i] - fdim[i]) / 2; mcount[i] = fdim[i]; mselect = true; } } if (mselect) { DEBUG { OUT << "memory subspace start=" << mstart[0] << ',' << mstart[1] << ',' << mstart[2] << ',' << mstart[3] << '/' << doffset << " count=" << mcount[0] << ',' << mcount[1] << ',' << mcount[2] << ',' << mcount[3] << '/' << doffset << "\nfile space is " << fdim[0] << ',' << fdim[1] << ',' << fdim[2] << ',' << fdim[3] << std::endl; } if (H5Sselect_hyperslab(mspace, H5S_SELECT_SET, &mstart[doffset], nullptr, &mcount[doffset], nullptr) < 0) throw hdf5::exception("unable to set hdf5 memory space"); } if (fselect) { DEBUG { OUT << "file subspace start=" << fstart[0] << ',' << fstart[1] << ',' << fstart[2] << ',' << fstart[3] << '/' << doffset << " count=" << fcount[0] << ',' << fcount[1] << ',' << fcount[2] << ',' << fcount[3] << '/' << doffset << "\nmem space is " << mdim[0] << ',' << mdim[1] << ',' << mdim[2] << ',' << mdim[3] << std::endl; } if (H5Sselect_hyperslab(fspace, H5S_SELECT_SET, &fstart[doffset], nullptr, &fcount[doffset], nullptr) < 0) throw hdf5::exception("unable to set hdf5 dataset space"); } } /*! * \brief Read object from file into buffer * \param file HDF5 file * \param object_path path to object dataset * \param mdims result matrix dimensions (with size 5) * \param buf pointer to parent acessible buffer * \tparam f_type buffer element type */ template void read_complex_matrix(hdf5::file &file, const std::string &object_path, const std::vector &mdims, typename mx_trait::complex_type *buf) { hsize_t fdims[max_dims] = { 1, 1, 0, 0 }; // (nslices, nmodes, nrows, ncols) hsize_t bdims[max_dims] = { 1, 1, 0, 0 }; // file and buffer dimensions unsigned long nelements = 0; int dim_offset = 0; int ndims = 0; int mdim_offset = 0; for (; (mdim_offset < max_dims-2) && (mdims[mdim_offset] == 1); mdim_offset++); // Create complex type hdf5::h5type complex_type(hdf5::create_complex_type()); // Open dataset hdf5::dataset dataset(H5Dopen(file, object_path.c_str(), H5P_DEFAULT)); if (! dataset.valid()) throw hdf5::exception("unable to open dataset"); hdf5::dataspace dataspace(H5Dget_space(dataset)); if (! dataspace.valid()) throw hdf5::exception("unable to open data space for dataset"); ndims = H5Sget_simple_extent_ndims(dataspace); if (ndims < min_dims || ndims > max_dims) throw hdf5::exception("wrong number of dimensions in dataset"); dim_offset = max_dims - ndims; if (H5Sget_simple_extent_dims(dataspace, &fdims[dim_offset], NULL) != ndims) throw hdf5::exception("dimension error"); for (int i=max_dims-2; i; i--) { if (fdims[i-1] != mdims[i]) { if (i != mdim_offset) throw hdf5::exception("dimension mismatch"); } } // Create memory space and adapt data space bdims[0] = fdims[0]; // nslices bdims[1] = fdims[1]; // nmodes bdims[2] = mdims[3]; // nrows wanted bdims[3] = mdims[4]; // ncols wanted DEBUG { OUT << "bdims(" << bdims[0] << ',' << bdims[1] << ',' << bdims[2] << '/' << fdims[2] << ',' << bdims[3] << '/' << fdims[3] << ')' << dim_offset << std::endl; } hdf5::dataspace memspace(H5Screate_simple(ndims, &bdims[dim_offset], nullptr)); if (! memspace.valid()) throw hdf5::exception("unable to create memory data space"); if ((bdims[2] != fdims[2]) || (bdims[3] != fdims[3])) adapt_spaces(memspace, dataspace, bdims, fdims, dim_offset); // Read dataset if (H5Dread(dataset, complex_type, memspace, dataspace, H5P_DEFAULT, buf) < 0) throw hdf5::exception("unable to read dataset"); DEBUG { int last = bdims[max_dims-2] * bdims[max_dims-1] - 1; OUT << "read finished - [0]=(" << buf[0].real << ',' << buf[0].imag << ") [" << last << "]=(" << buf[last].real << ',' << buf[last].imag << ')' << std::endl; } } /*! * \brief Open HDF5 file * \param file_path path to HDF5 file * \return HDF5 file object id */ hid_t open_file(const std::string &file_path) { hdf5::file file(H5Fopen(file_path.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)); if (! file.valid()) throw hdf5::exception("unable to open file"); return file.grab(); } /*! * \brief Nonzero dimension size? * \param dims size of twodimensional object * \tparam T type of dimension size * \return are dimension sizes positive? */ template bool dims_ok (const T *dims) noexcept { return dims[0] > 0 || dims[1] > 0; } /*! * \brief Drop leading dimensions of size 1 * \param dim dimsension sizes * \param max maximum dimensions * \tparam T type of dimension size * \return number of dimsension of size bigger than 1 */ template int collapse_dims (const std::vector &dim, int max) noexcept { int i=0; for (; i max_dims) throw hdf5::exception("wrong number of dimensions in dataset"); dim_offset = max_dims - ndims; if (H5Sget_simple_extent_dims(dataspace, &dims[dim_offset], NULL) != ndims) throw hdf5::exception("unable to get dimension sizes"); } /*! * \brief Read in objects with multiple processes * * \param nprocs number of read processes * \param dims desired result dimensions (or [0, 0] to get the default behaviour) * \param object_path path to hdf5 object within the files * \param file_paths paths to hdf5 objects * \param r_stat fill in if not null * \tparam f_type float or double result type * \return MATLAB result array */ template mxArray* read_objects_parallel(int nprocs, std::array &dims, const std::string &object_path, const std::vector &file_paths, data_prep::read_stat *r_stat) { try { std::vector mdims({ 1, 1, 1, 0, 0 }); // extended matrix dimensions hsize_t odims[max_dims] = { 1, 1, 0, 0 }; // extended object dimensions (nobjects, nslices, nmodes, nrows, ncols) // Assign matrix dimension values { hdf5::file file(open_file(file_paths[0])); get_object_size(file, object_path, odims); } for (unsigned int i=1; i 1) { mdims[max_dims - nmdims] = (mwSize)file_paths.size(); nmdims++; } std::vector matrix_dims(mdims); // matrix dimensions matrix_dims.erase(matrix_dims.begin(), matrix_dims.begin() + max_dims + 1 - nmdims); // cut away extension dimensions // Create space for MATLAB array mx_ptr matrix; { std::vector rdims(matrix_dims); std::reverse(rdims.begin(), rdims.end()); matrix.reset(mxCreateNumericArray(nmdims, &rdims[0], mx_trait::class_id, mxCOMPLEX)); } if (! matrix.get()) throw std::runtime_error("matrix creation failed"); // Prepare (shared) memory buffer // HACK: misuse error message area as array of bad file indices // There should be space for the max number of handled files plus the length field const unsigned int message_size = error_collector::singleton->in_collect_mode ? std::max((file_paths.size() / nprocs + 2) * sizeof(uint32_t), 64ul) : 0; mp::buf::complex_type> buf(mx_trait::get_complex(matrix.get()), matrix_dims, nprocs, message_size); auto r_time = std::chrono::high_resolution_clock::now(); // Spawn processes if needed mp::run::complex_type>(nprocs, buf, [&file_paths, &object_path, &mdims](int proc, mp::buf::complex_type> &buf) { // Read objects data into buffer typename mx_trait::complex_type *data_buf = buf.get(proc); int first = buf.offset(proc); int last = buf.offset(proc+1); for (int obj=first; obj(file, object_path, mdims, &data_buf[offset]); } catch (std::exception &ex) { error_collector::singleton->error(read_error(obj, ex.what())); } catch (...) { error_collector::singleton->error(read_error(obj, "mysterious read error")); } } if (proc && error_collector::singleton->in_collect_mode) { // HACK: misuse error message area as array of bad file indices for this process // Layout: [ N | elem0 | elem1 | ... | elemN-1 ] uint32_t *msg_area = reinterpret_cast(buf.get_msg(proc)); auto &bad_index = error_collector::singleton->bad_index; msg_area[0] = bad_index.size(); std::copy(bad_index.begin(), bad_index.end(), &msg_area[1]); } }); // Set statistics (not correct for error collection mode) if (r_stat) { r_stat->seconds = std::chrono::duration(std::chrono::high_resolution_clock::now() - r_time).count(); r_stat->nbytes = buf.length * sizeof(typename mx_trait::complex_type); } // Collect bad indices in error collection mode if (error_collector::singleton->in_collect_mode) { DEBUG { OUT << "Collecting bad indices:" << std::endl; } for (int proc=1; proc(buf.get_msg(proc)); uint32_t nbad = msg_area[0]; DEBUG { OUT << " process " << proc << ": " << nbad << " indices" << std::endl; } if (nbad > (file_paths.size() / nprocs + 1)) throw std::runtime_error("transfer of bad indices failed"); for (uint32_t i=1; i<=nbad; i++) error_collector::singleton->add(msg_area[i]); } } // Return result return matrix.release(); } catch (std::exception &ex) { mexErrMsgIdAndTxt("ptycho:read:failed", "%s", ex.what()); return nullptr; } } /*! * \brief Convert vector to MATLAB array * \param vec vector to be converted * \return MATLAB array */ mxArray* vector_to_array(std::vector &vec) { mwSize vlen[2] = {1, vec.size()}; mx_ptr arr(mxCreateNumericArray(2, vlen, mxUINT32_CLASS, mxREAL)); if (! arr.get()) throw std::runtime_error("unable to create bad indices array"); if (! vec.empty()) { auto* element = mxGetUint32s(arr.get()); if (! element) throw std::runtime_error("unable to retrieve pointer for bad indices array elements"); for (auto& e : vec) *element++ = e+1; } return arr.release(); } } // namespace namespace data_prep { mxArray* read_object_data(int nprocs, precision::type prec, std::array &dims, const std::string &object_path, const std::vector &file_paths, mxArray **bad_file_idx, read_stat *r_stat) { if (! file_paths.size()) return nullptr; if (nprocs <= 0) nprocs = 1; if (nprocs > file_paths.size()) nprocs = (int)file_paths.size(); error_collector errcol(bad_file_idx != nullptr); mx_ptr result; { if (prec == precision::type::Double) result.reset(read_objects_parallel(nprocs, dims, object_path, file_paths, r_stat)); else result.reset(read_objects_parallel(nprocs, dims, object_path, file_paths, r_stat)); } if (errcol.in_collect_mode) *bad_file_idx = vector_to_array(errcol.bad_index); return result.release(); } } // namespace data_prep