/**
 * MOAB, a Mesh-Oriented datABase, is a software component for creating,
 * storing and accessing finite element mesh data.
 *
 * Copyright 2004 Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
 * retains certain rights in this software.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 */

//-------------------------------------------------------------------------
// Filename      : ReadHDF5.cpp
//
// Purpose       : HDF5 Writer
//
// Creator       : Jason Kraftcheck
//
// Creation Date : 04/18/04
//-------------------------------------------------------------------------

#include <cassert>
#include "moab/MOABConfig.h"
/* Include our MPI header before any HDF5 because otherwise
   it will get included indirectly by HDF5 */
#ifdef MOAB_HAVE_MPI
#include "moab_mpi.h"
#include "moab/ParallelComm.hpp"
#endif
#include <H5Tpublic.h>
#include <H5Ppublic.h>
#include <H5Epublic.h>
#include "moab/Interface.hpp"
#include "Internals.hpp"
#include "MBTagConventions.hpp"
#include "ReadHDF5.hpp"
#include "moab/CN.hpp"
#include "moab/FileOptions.hpp"
#include "moab/CpuTimer.hpp"
#ifdef MOAB_HAVE_HDF5_PARALLEL
#include <H5FDmpi.h>
#include <H5FDmpio.h>
#endif
//#include "WriteHDF5.hpp"

#include <cstdlib>
#include <cstring>
#include <limits>
#include <functional>
#include <iostream>

#include "IODebugTrack.hpp"
#include "ReadHDF5Dataset.hpp"
#include "ReadHDF5VarLen.hpp"
#include "moab_mpe.h"

namespace moab
{

/* If true, coordinates are read in blocked format (all X values before
 * Y values before Z values.) If undefined, then all coordinates for a
 * given vertex are read at the same time.
 */
const bool DEFAULT_BLOCKED_COORDINATE_IO = false;

/* If true, file is opened first by root node only to read summary,
 * file is the closed and the summary is broadcast to all nodes, after
 * which all nodes open file in parallel to read data. If undefined,
 * file is opened once in parallel and all nodes read summary data.
 */
const bool DEFAULT_BCAST_SUMMARY = true;

/* If true and all processors are to read the same block of data,
 * read it on one and broadcast to others rather than using collective
 * io
 */
const bool DEFAULT_BCAST_DUPLICATE_READS = true;

#define READ_HDF5_BUFFER_SIZE ( 128 * 1024 * 1024 )

#define assert_range( PTR, CNT )            \
    assert( ( PTR ) >= (void*)dataBuffer ); \
    assert( ( ( PTR ) + ( CNT ) ) <= (void*)( dataBuffer + bufferSize ) );

// Call \c error function during HDF5 library errors to make
// it easier to trap such errors in the debugger. This function
// gets registered with the HDF5 library as a callback. It
// works the same as the default (H5Eprint), except that it
// also calls the \c error function as a no-op.
#if defined( H5E_auto_t_vers ) && H5E_auto_t_vers > 1
static herr_t handle_hdf5_error( hid_t stack, void* data )
{
    ReadHDF5::HDF5ErrorHandler* h = reinterpret_cast< ReadHDF5::HDF5ErrorHandler* >( data );
    herr_t result                 = 0;
    if( h->func ) result = ( *h->func )( stack, h->data );MB_CHK_ERR_CONT( MB_FAILURE );
    return result;
}
#else
static herr_t handle_hdf5_error( void* data )
{
    ReadHDF5::HDF5ErrorHandler* h = reinterpret_cast< ReadHDF5::HDF5ErrorHandler* >( data );
    herr_t result                 = 0;
    if( h->func ) result = ( *h->func )( h->data );MB_CHK_ERR_CONT( MB_FAILURE );
    return result;
}
#endif

static void copy_sorted_file_ids( const EntityHandle* sorted_ids, long num_ids, Range& results )
{
    Range::iterator hint = results.begin();
    long i               = 0;
    while( i < num_ids )
    {
        EntityHandle start = sorted_ids[i];
        for( ++i; i < num_ids && sorted_ids[i] == 1 + sorted_ids[i - 1]; ++i )
            ;
        hint = results.insert( hint, start, sorted_ids[i - 1] );
    }
}

static void intersect( const mhdf_EntDesc& group, const Range& range, Range& result )
{
    Range::const_iterator s, e;
    s = Range::lower_bound( range.begin(), range.end(), group.start_id );
    e = Range::lower_bound( s, range.end(), group.start_id + group.count );
    result.merge( s, e );
}

#define debug_barrier() debug_barrier_line( __LINE__ )
void ReadHDF5::debug_barrier_line( int lineno )
{
#ifdef MOAB_HAVE_MPI
    if( mpiComm )
    {
        const unsigned threshold   = 2;
        static unsigned long count = 0;
        if( dbgOut.get_verbosity() >= threshold )
        {
            dbgOut.printf( threshold, "*********** Debug Barrier %lu (@%d)***********\n", ++count, lineno );
            MPI_Barrier( *mpiComm );
        }
    }
#else
    if( lineno ) {}
#endif
}

class CheckOpenReadHDF5Handles
{
    int fileline;
    mhdf_FileHandle handle;
    int enter_count;

  public:
    CheckOpenReadHDF5Handles( mhdf_FileHandle file, int line )
        : fileline( line ), handle( file ), enter_count( mhdf_countOpenHandles( file ) )
    {
    }
    ~CheckOpenReadHDF5Handles()
    {
        int new_count = mhdf_countOpenHandles( handle );
        if( new_count != enter_count )
        {
            std::cout << "Leaked HDF5 object handle in function at " << __FILE__ << ":" << fileline << std::endl
                      << "Open at entrance: " << enter_count << std::endl
                      << "Open at exit:     " << new_count << std::endl;
        }
    }
};

#ifdef NDEBUG
#define CHECK_OPEN_HANDLES
#else
#define CHECK_OPEN_HANDLES CheckOpenReadHDF5Handles check_open_handles_( filePtr, __LINE__ )
#endif

ReaderIface* ReadHDF5::factory( Interface* iface )
{
    return new ReadHDF5( iface );
}

ReadHDF5::ReadHDF5( Interface* iface )
    : bufferSize( READ_HDF5_BUFFER_SIZE ), dataBuffer( NULL ), iFace( iface ), filePtr( 0 ), fileInfo( NULL ),
      readUtil( NULL ), handleType( 0 ), indepIO( H5P_DEFAULT ), collIO( H5P_DEFAULT ), myPcomm( NULL ),
      debugTrack( false ), dbgOut( stderr ), nativeParallel( false ), mpiComm( NULL ),
      blockedCoordinateIO( DEFAULT_BLOCKED_COORDINATE_IO ), bcastSummary( DEFAULT_BCAST_SUMMARY ),
      bcastDuplicateReads( DEFAULT_BCAST_DUPLICATE_READS ), setMeta( 0 ), timer( NULL ), cputime( false )
{
}

ErrorCode ReadHDF5::init()
{
    ErrorCode rval;

    if( readUtil ) return MB_SUCCESS;

    indepIO = collIO = H5P_DEFAULT;
    // WriteHDF5::register_known_tag_types(iFace);

    handleType = H5Tcopy( H5T_NATIVE_ULONG );
    if( handleType < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    if( H5Tset_size( handleType, sizeof( EntityHandle ) ) < 0 )
    {
        H5Tclose( handleType );
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    rval = iFace->query_interface( readUtil );
    if( MB_SUCCESS != rval )
    {
        H5Tclose( handleType );
        MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    idMap.clear();
    fileInfo   = 0;
    debugTrack = false;
    myPcomm    = 0;

    return MB_SUCCESS;
}

ReadHDF5::~ReadHDF5()
{
    if( !readUtil )  // init() failed.
        return;

    delete[] setMeta;
    setMeta = 0;
    iFace->release_interface( readUtil );
    H5Tclose( handleType );
}

ErrorCode ReadHDF5::set_up_read( const char* filename, const FileOptions& opts )
{
    ErrorCode rval;
    mhdf_Status status;
    indepIO = collIO = H5P_DEFAULT;
    mpiComm          = 0;

    if( MB_SUCCESS != init() ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

#if defined( H5Eget_auto_vers ) && H5Eget_auto_vers > 1
    herr_t err = H5Eget_auto( H5E_DEFAULT, &errorHandler.func, &errorHandler.data );
#else
    herr_t err = H5Eget_auto( &errorHandler.func, &errorHandler.data );
#endif
    if( err < 0 )
    {
        errorHandler.func = 0;
        errorHandler.data = 0;
    }
    else
    {
#if defined( H5Eset_auto_vers ) && H5Eset_auto_vers > 1
        err = H5Eset_auto( H5E_DEFAULT, &handle_hdf5_error, &errorHandler );
#else
        err           = H5Eset_auto( &handle_hdf5_error, &errorHandler );
#endif
        if( err < 0 )
        {
            errorHandler.func = 0;
            errorHandler.data = 0;
        }
    }

    // Set up debug output
    int tmpval;
    if( MB_SUCCESS == opts.get_int_option( "DEBUG_IO", 1, tmpval ) )
    {
        dbgOut.set_verbosity( tmpval );
        dbgOut.set_prefix( "H5M " );
    }
    dbgOut.limit_output_to_first_N_procs( 32 );

    // Enable some extra checks for reads. Note: amongst other things this
    // will print errors if the entire file is not read, so if doing a
    // partial read that is not a parallel read, this should be disabled.
    debugTrack = ( MB_SUCCESS == opts.get_null_option( "DEBUG_BINIO" ) );

    opts.get_toggle_option( "BLOCKED_COORDINATE_IO", DEFAULT_BLOCKED_COORDINATE_IO, blockedCoordinateIO );
    opts.get_toggle_option( "BCAST_SUMMARY", DEFAULT_BCAST_SUMMARY, bcastSummary );
    opts.get_toggle_option( "BCAST_DUPLICATE_READS", DEFAULT_BCAST_DUPLICATE_READS, bcastDuplicateReads );

    // Handle parallel options
    bool use_mpio  = ( MB_SUCCESS == opts.get_null_option( "USE_MPIO" ) );
    rval           = opts.match_option( "PARALLEL", "READ_PART" );
    bool parallel  = ( rval != MB_ENTITY_NOT_FOUND );
    nativeParallel = ( rval == MB_SUCCESS );
    if( use_mpio && !parallel )
    {
        MB_SET_ERR( MB_NOT_IMPLEMENTED, "'USE_MPIO' option specified w/out 'PARALLEL' option" );
    }

    // This option is intended for testing purposes only, and thus
    // is not documented anywhere.  Decreasing the buffer size can
    // expose bugs that would otherwise only be seen when reading
    // very large files.
    rval = opts.get_int_option( "BUFFER_SIZE", bufferSize );
    if( MB_SUCCESS != rval ) { bufferSize = READ_HDF5_BUFFER_SIZE; }
    else if( bufferSize < (int)std::max( sizeof( EntityHandle ), sizeof( void* ) ) )
    {
        MB_CHK_ERR( MB_INVALID_SIZE );
    }

    dataBuffer = (char*)malloc( bufferSize );
    if( !dataBuffer ) MB_CHK_ERR( MB_MEMORY_ALLOCATION_FAILED );

    if( use_mpio || nativeParallel )
    {

#ifndef MOAB_HAVE_HDF5_PARALLEL
        free( dataBuffer );
        dataBuffer = NULL;
        MB_SET_ERR( MB_NOT_IMPLEMENTED, "MOAB not configured with parallel HDF5 support" );
#else
        MPI_Info info = MPI_INFO_NULL;
        std::string cb_size;
        rval = opts.get_str_option( "CB_BUFFER_SIZE", cb_size );
        if( MB_SUCCESS == rval )
        {
            MPI_Info_create( &info );
            MPI_Info_set( info, const_cast< char* >( "cb_buffer_size" ), const_cast< char* >( cb_size.c_str() ) );
        }

        int pcomm_no = 0;
        rval         = opts.get_int_option( "PARALLEL_COMM", pcomm_no );
        if( rval == MB_TYPE_OUT_OF_RANGE ) { MB_SET_ERR( rval, "Invalid value for PARALLEL_COMM option" ); }
        myPcomm = ParallelComm::get_pcomm( iFace, pcomm_no );
        if( 0 == myPcomm ) { myPcomm = new ParallelComm( iFace, MPI_COMM_WORLD ); }
        const int rank = myPcomm->proc_config().proc_rank();
        dbgOut.set_rank( rank );
        dbgOut.limit_output_to_first_N_procs( 32 );
        mpiComm = new MPI_Comm( myPcomm->proc_config().proc_comm() );

#ifndef H5_MPI_COMPLEX_DERIVED_DATATYPE_WORKS
        dbgOut.print( 1, "H5_MPI_COMPLEX_DERIVED_DATATYPE_WORKS is not defined\n" );
#endif

        // Open the file in serial on root to read summary
        dbgOut.tprint( 1, "Getting file summary\n" );
        fileInfo = 0;

        hid_t file_prop;
        if( bcastSummary )
        {
            unsigned long size = 0;
            if( rank == 0 )
            {
                file_prop = H5Pcreate( H5P_FILE_ACCESS );
                err       = H5Pset_fapl_mpio( file_prop, MPI_COMM_SELF, MPI_INFO_NULL );
                assert( file_prop >= 0 );
                assert( err >= 0 );
                filePtr = mhdf_openFileWithOpt( filename, 0, NULL, handleType, file_prop, &status );
                H5Pclose( file_prop );

                if( filePtr )
                {
                    fileInfo = mhdf_getFileSummary( filePtr, handleType, &status,
                                                    0 );  // no extra set info
                    if( !is_error( status ) )
                    {
                        size             = fileInfo->total_size;
                        fileInfo->offset = (unsigned char*)fileInfo;
                    }
                }
                mhdf_closeFile( filePtr, &status );
                if( fileInfo && mhdf_isError( &status ) )
                {
                    free( fileInfo );
                    fileInfo = NULL;
                }
            }

            dbgOut.tprint( 1, "Communicating file summary\n" );
            int mpi_err = MPI_Bcast( &size, 1, MPI_UNSIGNED_LONG, 0, myPcomm->proc_config().proc_comm() );
            if( mpi_err || !size ) return MB_FAILURE;

            if( rank != 0 ) fileInfo = reinterpret_cast< mhdf_FileDesc* >( malloc( size ) );

            MPI_Bcast( fileInfo, size, MPI_BYTE, 0, myPcomm->proc_config().proc_comm() );

            if( rank != 0 ) mhdf_fixFileDesc( fileInfo, reinterpret_cast< mhdf_FileDesc* >( fileInfo->offset ) );
        }

        file_prop = H5Pcreate( H5P_FILE_ACCESS );
        err       = H5Pset_fapl_mpio( file_prop, myPcomm->proc_config().proc_comm(), info );
        assert( file_prop >= 0 );
        assert( err >= 0 );

        collIO = H5Pcreate( H5P_DATASET_XFER );
        assert( collIO > 0 );
        err = H5Pset_dxpl_mpio( collIO, H5FD_MPIO_COLLECTIVE );
        assert( err >= 0 );
        indepIO = nativeParallel ? H5P_DEFAULT : collIO;

        // Re-open file in parallel
        dbgOut.tprintf( 1, "Opening \"%s\" for parallel IO\n", filename );
        filePtr = mhdf_openFileWithOpt( filename, 0, NULL, handleType, file_prop, &status );

        H5Pclose( file_prop );
        if( !filePtr )
        {
            free( dataBuffer );
            dataBuffer = NULL;
            H5Pclose( indepIO );
            if( collIO != indepIO ) H5Pclose( collIO );
            collIO = indepIO = H5P_DEFAULT;
            MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) );
        }

        if( !bcastSummary )
        {
            fileInfo = mhdf_getFileSummary( filePtr, handleType, &status, 0 );
            if( is_error( status ) )
            {
                free( dataBuffer );
                dataBuffer = NULL;
                mhdf_closeFile( filePtr, &status );
                MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
            }
        }
#endif  // HDF5_PARALLEL
    }
    else
    {
        // Open the file
        filePtr = mhdf_openFile( filename, 0, NULL, handleType, &status );
        if( !filePtr )
        {
            free( dataBuffer );
            dataBuffer = NULL;
            MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) );
        }

        // Get file info
        fileInfo = mhdf_getFileSummary( filePtr, handleType, &status, 0 );
        if( is_error( status ) )
        {
            free( dataBuffer );
            dataBuffer = NULL;
            mhdf_closeFile( filePtr, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }

    ReadHDF5Dataset::default_hyperslab_selection_limit();
    int hslimit;
    rval = opts.get_int_option( "HYPERSLAB_SELECT_LIMIT", hslimit );
    if( MB_SUCCESS == rval && hslimit > 0 )
        ReadHDF5Dataset::set_hyperslab_selection_limit( hslimit );
    else
        ReadHDF5Dataset::default_hyperslab_selection_limit();
    if( MB_SUCCESS != opts.get_null_option( "HYPERSLAB_OR" ) &&
        ( MB_SUCCESS == opts.get_null_option( "HYPERSLAB_APPEND" ) || HDF5_can_append_hyperslabs() ) )
    {
        ReadHDF5Dataset::append_hyperslabs();
        if( MB_SUCCESS != opts.get_int_option( "HYPERSLAB_SELECT_LIMIT", hslimit ) )
            ReadHDF5Dataset::set_hyperslab_selection_limit( std::numeric_limits< int >::max() );
        dbgOut.print( 1, "Using H5S_APPEND for hyperslab selection\n" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::clean_up_read( const FileOptions& )
{
    HDF5ErrorHandler handler;
#if defined( H5Eget_auto_vers ) && H5Eget_auto_vers > 1
    herr_t err = H5Eget_auto( H5E_DEFAULT, &handler.func, &handler.data );
#else
    herr_t err = H5Eget_auto( &handler.func, &handler.data );
#endif
    if( err >= 0 && handler.func == &handle_hdf5_error )
    {
        assert( handler.data == &errorHandler );
#if defined( H5Eget_auto_vers ) && H5Eget_auto_vers > 1
        H5Eset_auto( H5E_DEFAULT, errorHandler.func, errorHandler.data );
#else
        H5Eset_auto( errorHandler.func, errorHandler.data );
#endif
    }

    free( dataBuffer );
    dataBuffer = NULL;
    free( fileInfo );
    fileInfo = NULL;
    delete mpiComm;
    mpiComm = 0;

    if( indepIO != H5P_DEFAULT ) H5Pclose( indepIO );
    if( collIO != indepIO ) H5Pclose( collIO );
    collIO = indepIO = H5P_DEFAULT;

    delete[] setMeta;
    setMeta = 0;

    mhdf_Status status;
    mhdf_closeFile( filePtr, &status );
    filePtr = 0;
    return is_error( status ) ? MB_FAILURE : MB_SUCCESS;
}

ErrorCode ReadHDF5::load_file( const char* filename, const EntityHandle* file_set, const FileOptions& opts,
                               const ReaderIface::SubsetList* subset_list, const Tag* file_id_tag )
{
    ErrorCode rval;

    rval = set_up_read( filename, opts );
    if( MB_SUCCESS != rval )
    {
        clean_up_read( opts );
        return rval;
    }
    // See if we need to report times

    rval = opts.get_null_option( "CPUTIME" );
    if( MB_SUCCESS == rval )
    {
        cputime = true;
        timer   = new CpuTimer;
        for( int i = 0; i < NUM_TIMES; i++ )
            _times[i] = 0;
    }

    // We read the entire set description table regardless of partial
    // or complete reads or serial vs parallel reads
    rval = read_all_set_meta();

    if( cputime ) _times[SET_META_TIME] = timer->time_elapsed();
    if( subset_list && MB_SUCCESS == rval )
        rval = load_file_partial( subset_list->tag_list, subset_list->tag_list_length, subset_list->num_parts,
                                  subset_list->part_number, opts );
    else
        rval = load_file_impl( opts );

    if( MB_SUCCESS == rval && file_id_tag )
    {
        dbgOut.tprint( 1, "Storing file IDs in tag\n" );
        rval = store_file_ids( *file_id_tag );
    }
    ErrorCode rval3 = opts.get_null_option( "STORE_SETS_FILEIDS" );
    if( MB_SUCCESS == rval3 )
    {
        rval = store_sets_file_ids();
        if( MB_SUCCESS != rval ) return rval;
    }

    if( cputime ) _times[STORE_FILE_IDS_TIME] = timer->time_elapsed();

    if( MB_SUCCESS == rval && 0 != file_set )
    {
        dbgOut.tprint( 1, "Reading QA records\n" );
        rval = read_qa( *file_set );
    }

    if( cputime ) _times[READ_QA_TIME] = timer->time_elapsed();
    dbgOut.tprint( 1, "Cleaning up\n" );
    ErrorCode rval2 = clean_up_read( opts );
    if( rval == MB_SUCCESS && rval2 != MB_SUCCESS ) rval = rval2;

    if( MB_SUCCESS == rval )
        dbgOut.tprint( 1, "Read finished.\n" );
    else
    {
        std::string msg;
        iFace->get_last_error( msg );
        dbgOut.tprintf( 1, "READ FAILED (ERROR CODE %s): %s\n", ErrorCodeStr[rval], msg.c_str() );
    }

    if( cputime )
    {
        _times[TOTAL_TIME] = timer->time_since_birth();
        print_times();
        delete timer;
    }
    if( H5P_DEFAULT != collIO ) H5Pclose( collIO );
    if( H5P_DEFAULT != indepIO ) H5Pclose( indepIO );
    collIO = indepIO = H5P_DEFAULT;

    return rval;
}

ErrorCode ReadHDF5::load_file_impl( const FileOptions& )
{
    ErrorCode rval;
    mhdf_Status status;
    int i;

    CHECK_OPEN_HANDLES;

    dbgOut.tprint( 1, "Reading all nodes...\n" );
    Range ids;
    if( fileInfo->nodes.count )
    {
        ids.insert( fileInfo->nodes.start_id, fileInfo->nodes.start_id + fileInfo->nodes.count - 1 );
        rval = read_nodes( ids );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    dbgOut.tprint( 1, "Reading all element connectivity...\n" );
    std::vector< int > polyhedra;  // Need to do these last so that faces are loaded
    for( i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        if( CN::EntityTypeFromName( fileInfo->elems[i].type ) == MBPOLYHEDRON )
        {
            polyhedra.push_back( i );
            continue;
        }

        rval = read_elems( i );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }
    for( std::vector< int >::iterator it = polyhedra.begin(); it != polyhedra.end(); ++it )
    {
        rval = read_elems( *it );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    dbgOut.tprint( 1, "Reading all sets...\n" );
    ids.clear();
    if( fileInfo->sets.count )
    {
        ids.insert( fileInfo->sets.start_id, fileInfo->sets.start_id + fileInfo->sets.count - 1 );
        rval = read_sets( ids );
        if( rval != MB_SUCCESS ) { MB_SET_ERR( rval, "ReadHDF5 Failure" ); }
    }

    dbgOut.tprint( 1, "Reading all adjacencies...\n" );
    for( i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        if( !fileInfo->elems[i].have_adj ) continue;

        long table_len;
        hid_t table = mhdf_openAdjacency( filePtr, fileInfo->elems[i].handle, &table_len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        rval = read_adjacencies( table, table_len );
        mhdf_closeData( filePtr, table, &status );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    dbgOut.tprint( 1, "Reading all tags...\n" );
    for( i = 0; i < fileInfo->num_tag_desc; ++i )
    {
        rval = read_tag( i );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    dbgOut.tprint( 1, "Core read finished.  Cleaning up...\n" );
    return MB_SUCCESS;
}

ErrorCode ReadHDF5::find_int_tag( const char* name, int& index )
{
    for( index = 0; index < fileInfo->num_tag_desc; ++index )
        if( !strcmp( name, fileInfo->tags[index].name ) ) break;

    if( index == fileInfo->num_tag_desc )
    {
        MB_SET_ERR( MB_TAG_NOT_FOUND, "File does not contain subset tag '" << name << "'" );
    }

    if( fileInfo->tags[index].type != mhdf_INTEGER || fileInfo->tags[index].size != 1 )
    {
        MB_SET_ERR( MB_TAG_NOT_FOUND, "Tag ' " << name << "' does not contain single integer value" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::get_subset_ids( const ReaderIface::IDTag* subset_list, int subset_list_length, Range& file_ids )
{
    ErrorCode rval;

    for( int i = 0; i < subset_list_length; ++i )
    {
        int tag_index;
        rval = find_int_tag( subset_list[i].tag_name, tag_index );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

        Range tmp_file_ids;
        if( !subset_list[i].num_tag_values ) { rval = get_tagged_entities( tag_index, tmp_file_ids ); }
        else
        {
            std::vector< int > ids( subset_list[i].tag_values,
                                    subset_list[i].tag_values + subset_list[i].num_tag_values );
            std::sort( ids.begin(), ids.end() );
            rval = search_tag_values( tag_index, ids, tmp_file_ids );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }

        if( tmp_file_ids.empty() ) MB_CHK_ERR( MB_ENTITY_NOT_FOUND );

        if( i == 0 )
            file_ids.swap( tmp_file_ids );
        else
            file_ids = intersect( tmp_file_ids, file_ids );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::get_partition( Range& tmp_file_ids, int num_parts, int part_number )
{
    CHECK_OPEN_HANDLES;

    // Check that the tag only identified sets
    if( (unsigned long)fileInfo->sets.start_id > tmp_file_ids.front() )
    {
        dbgOut.print( 2, "Ignoring non-set entities with partition set tag\n" );
        tmp_file_ids.erase( tmp_file_ids.begin(), tmp_file_ids.lower_bound( (EntityHandle)fileInfo->sets.start_id ) );
    }
    unsigned long set_end = (unsigned long)fileInfo->sets.start_id + fileInfo->sets.count;
    if( tmp_file_ids.back() >= set_end )
    {
        dbgOut.print( 2, "Ignoring non-set entities with partition set tag\n" );
        tmp_file_ids.erase( tmp_file_ids.upper_bound( (EntityHandle)set_end ), tmp_file_ids.end() );
    }

    Range::iterator s   = tmp_file_ids.begin();
    size_t num_per_proc = tmp_file_ids.size() / num_parts;
    size_t num_extra    = tmp_file_ids.size() % num_parts;
    Range::iterator e;
    if( part_number < (long)num_extra )
    {
        s += ( num_per_proc + 1 ) * part_number;
        e = s;
        e += ( num_per_proc + 1 );
    }
    else
    {
        s += num_per_proc * part_number + num_extra;
        e = s;
        e += num_per_proc;
    }
    tmp_file_ids.erase( e, tmp_file_ids.end() );
    tmp_file_ids.erase( tmp_file_ids.begin(), s );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::load_file_partial( const ReaderIface::IDTag* subset_list, int subset_list_length, int num_parts,
                                       int part_number, const FileOptions& opts )
{
    mhdf_Status status;

    static MPEState mpe_event( "ReadHDF5", "yellow" );

    mpe_event.start( "gather parts" );

    CHECK_OPEN_HANDLES;

    for( int i = 0; i < subset_list_length; ++i )
    {
        dbgOut.printf( 2, "Select by \"%s\" with num_tag_values = %d\n", subset_list[i].tag_name,
                       subset_list[i].num_tag_values );
        if( subset_list[i].num_tag_values )
        {
            assert( 0 != subset_list[i].tag_values );
            dbgOut.printf( 2, "  \"%s\" values = { %d", subset_list[i].tag_name, subset_list[i].tag_values[0] );
            for( int j = 1; j < subset_list[i].num_tag_values; ++j )
                dbgOut.printf( 2, ", %d", subset_list[i].tag_values[j] );
            dbgOut.printf( 2, " }\n" );
        }
    }
    if( num_parts ) dbgOut.printf( 2, "Partition with num_parts = %d and part_number = %d\n", num_parts, part_number );

    dbgOut.tprint( 1, "RETRIEVING TAGGED ENTITIES\n" );

    Range file_ids;
    ErrorCode rval = get_subset_ids( subset_list, subset_list_length, file_ids );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[SUBSET_IDS_TIME] = timer->time_elapsed();

    if( num_parts )
    {
        /*if (num_parts>(int)file_ids.size())
        {
          MB_SET_ERR(MB_FAILURE, "Only " << file_ids.size() << " parts to distribute to " <<
        num_parts << " processes.");
        }*/
        rval = get_partition( file_ids, num_parts, part_number );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[GET_PARTITION_TIME] = timer->time_elapsed();

    dbgOut.print_ints( 4, "Set file IDs for partial read: ", file_ids );
    mpe_event.end();
    mpe_event.start( "gather related sets" );
    dbgOut.tprint( 1, "GATHERING ADDITIONAL ENTITIES\n" );

    enum RecusiveSetMode
    {
        RSM_NONE,
        RSM_SETS,
        RSM_CONTENTS
    };
    const char* const set_opts[] = { "NONE", "SETS", "CONTENTS", NULL };
    int child_mode;
    rval = opts.match_option( "CHILDREN", set_opts, child_mode );
    if( MB_ENTITY_NOT_FOUND == rval )
        child_mode = RSM_CONTENTS;
    else if( MB_SUCCESS != rval )
    {
        MB_SET_ERR( rval, "Invalid value for 'CHILDREN' option" );
    }
    int content_mode;
    rval = opts.match_option( "SETS", set_opts, content_mode );
    if( MB_ENTITY_NOT_FOUND == rval )
        content_mode = RSM_CONTENTS;
    else if( MB_SUCCESS != rval )
    {
        MB_SET_ERR( rval, "Invalid value for 'SETS' option" );
    }

    // If we want the contents of contained/child sets,
    // search for them now (before gathering the non-set contents
    // of the sets.)
    Range sets;
    intersect( fileInfo->sets, file_ids, sets );
    if( content_mode == RSM_CONTENTS || child_mode == RSM_CONTENTS )
    {
        dbgOut.tprint( 1, "  doing read_set_ids_recursive\n" );
        rval = read_set_ids_recursive( sets, content_mode == RSM_CONTENTS, child_mode == RSM_CONTENTS );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[GET_SET_IDS_TIME] = timer->time_elapsed();
    debug_barrier();

    // Get elements and vertices contained in sets
    dbgOut.tprint( 1, "  doing get_set_contents\n" );
    rval = get_set_contents( sets, file_ids );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[GET_SET_CONTENTS_TIME] = timer->time_elapsed();

    dbgOut.print_ints( 5, "File IDs for partial read: ", file_ids );
    debug_barrier();
    mpe_event.end();
    dbgOut.tprint( 1, "GATHERING NODE IDS\n" );

    // Figure out the maximum dimension of entity to be read
    int max_dim = 0;
    for( int i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        EntityType type = CN::EntityTypeFromName( fileInfo->elems[i].type );
        if( type <= MBVERTEX || type >= MBENTITYSET )
        {
            assert( false );  // For debug code die for unknown element types
            continue;         // For release code, skip unknown element types
        }
        int dim = CN::Dimension( type );
        if( dim > max_dim )
        {
            Range subset;
            intersect( fileInfo->elems[i].desc, file_ids, subset );
            if( !subset.empty() ) max_dim = dim;
        }
    }
#ifdef MOAB_HAVE_MPI
    if( nativeParallel )
    {
        int send = max_dim;
        MPI_Allreduce( &send, &max_dim, 1, MPI_INT, MPI_MAX, *mpiComm );
    }
#endif

    // If input contained any polyhedra, then need to get faces
    // of the polyhedra before the next loop because we need to
    // read said faces in that loop.
    for( int i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        EntityType type = CN::EntityTypeFromName( fileInfo->elems[i].type );
        if( type != MBPOLYHEDRON ) continue;

        debug_barrier();
        dbgOut.print( 2, "    Getting polyhedra faces\n" );
        mpe_event.start( "reading connectivity for ", fileInfo->elems[i].handle );

        Range polyhedra;
        intersect( fileInfo->elems[i].desc, file_ids, polyhedra );
        rval = read_elems( i, polyhedra, &file_ids );
        mpe_event.end( rval );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[GET_POLYHEDRA_TIME] = timer->time_elapsed();
    // Get node file ids for all elements
    Range nodes;
    intersect( fileInfo->nodes, file_ids, nodes );
    for( int i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        EntityType type = CN::EntityTypeFromName( fileInfo->elems[i].type );
        if( type <= MBVERTEX || type >= MBENTITYSET )
        {
            assert( false );  // For debug code die for unknown element types
            continue;         // For release code, skip unknown element types
        }
        if( MBPOLYHEDRON == type ) continue;

        debug_barrier();
        dbgOut.printf( 2, "    Getting element node IDs for: %s\n", fileInfo->elems[i].handle );

        Range subset;
        intersect( fileInfo->elems[i].desc, file_ids, subset );
        mpe_event.start( "reading connectivity for ", fileInfo->elems[i].handle );

        // If dimension is max_dim, then we can create the elements now
        // so we don't have to read the table again later (connectivity
        // will be fixed up after nodes are created when update_connectivity())
        // is called.  For elements of a smaller dimension, we just build
        // the node ID range now because a) we'll have to read the whole
        // connectivity table again later, and b) we don't want to worry
        // about accidentally creating multiple copies of the same element.
        if( CN::Dimension( type ) == max_dim )
            rval = read_elems( i, subset, &nodes );
        else
            rval = read_elems( i, subset, nodes );
        mpe_event.end( rval );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }
    if( cputime ) _times[GET_ELEMENTS_TIME] = timer->time_elapsed();
    debug_barrier();
    mpe_event.start( "read coords" );
    dbgOut.tprintf( 1, "READING NODE COORDINATES (%lu nodes in %lu selects)\n", (unsigned long)nodes.size(),
                    (unsigned long)nodes.psize() );

    // Read node coordinates and create vertices in MOAB
    // NOTE: This populates the RangeMap with node file ids,
    //       which is expected by read_node_adj_elems.
    rval = read_nodes( nodes );
    mpe_event.end( rval );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[GET_NODES_TIME] = timer->time_elapsed();

    debug_barrier();
    dbgOut.tprint( 1, "READING ELEMENTS\n" );

    // Decide if we need to read additional elements
    enum SideMode
    {
        SM_EXPLICIT,
        SM_NODES,
        SM_SIDES
    };
    int side_mode;
    const char* const options[] = { "EXPLICIT", "NODES", "SIDES", 0 };
    rval                        = opts.match_option( "ELEMENTS", options, side_mode );
    if( MB_ENTITY_NOT_FOUND == rval )
    {
        // If only nodes were specified, then default to "NODES", otherwise
        // default to "SIDES".
        if( 0 == max_dim )
            side_mode = SM_NODES;
        else
            side_mode = SM_SIDES;
    }
    else if( MB_SUCCESS != rval )
    {
        MB_SET_ERR( rval, "Invalid value for 'ELEMENTS' option" );
    }

    if( side_mode == SM_SIDES /*ELEMENTS=SIDES*/ && max_dim == 0 /*node-based*/ )
    {
        // Read elements until we find something. Once we find something,
        // read only elements of the same dimension. NOTE: loop termination
        // criterion changes on both sides (max_dim can be changed in loop
        // body).
        for( int dim = 3; dim >= max_dim; --dim )
        {
            for( int i = 0; i < fileInfo->num_elem_desc; ++i )
            {
                EntityType type = CN::EntityTypeFromName( fileInfo->elems[i].type );
                if( CN::Dimension( type ) == dim )
                {
                    debug_barrier();
                    dbgOut.tprintf( 2, "    Reading node-adjacent elements for: %s\n", fileInfo->elems[i].handle );
                    mpe_event.start( "reading connectivity for ", fileInfo->elems[i].handle );
                    Range ents;
                    rval = read_node_adj_elems( fileInfo->elems[i] );
                    mpe_event.end( rval );
                    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
                    if( !ents.empty() ) max_dim = 3;
                }
            }
        }
    }

    if( cputime ) _times[GET_NODEADJ_TIME] = timer->time_elapsed();
    Range side_entities;
    if( side_mode != SM_EXPLICIT /*ELEMENTS=NODES || ELEMENTS=SIDES*/ )
    {
        if( 0 == max_dim ) max_dim = 4;
        // Now read any additional elements for which we've already read all
        // of the nodes.
        for( int dim = max_dim - 1; dim > 0; --dim )
        {
            for( int i = 0; i < fileInfo->num_elem_desc; ++i )
            {
                EntityType type = CN::EntityTypeFromName( fileInfo->elems[i].type );
                if( CN::Dimension( type ) == dim )
                {
                    debug_barrier();
                    dbgOut.tprintf( 2, "    Reading node-adjacent elements for: %s\n", fileInfo->elems[i].handle );
                    mpe_event.start( "reading connectivity for ", fileInfo->elems[i].handle );
                    rval = read_node_adj_elems( fileInfo->elems[i], &side_entities );
                    mpe_event.end( rval );
                    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
                }
            }
        }
    }

    // We need to do this here for polyhedra to be handled correctly.
    // We have to wait until the faces are read in the above code block,
    // but need to create the connectivity before doing update_connectivity,
    // which might otherwise delete polyhedra faces.
    if( cputime ) _times[GET_SIDEELEM_TIME] = timer->time_elapsed();

    debug_barrier();
    dbgOut.tprint( 1, "UPDATING CONNECTIVITY ARRAYS FOR READ ELEMENTS\n" );
    mpe_event.start( "updating connectivity for elements read before vertices" );
    rval = update_connectivity();
    mpe_event.end();
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[UPDATECONN_TIME] = timer->time_elapsed();

    dbgOut.tprint( 1, "READING ADJACENCIES\n" );
    for( int i = 0; i < fileInfo->num_elem_desc; ++i )
    {
        if (fileInfo->elems[i].have_adj  /*&&
        idMap.intersects(fileInfo->elems[i].desc.start_id, fileInfo->elems[i].desc.count) */)
        {
            mpe_event.start( "reading adjacencies for ", fileInfo->elems[i].handle );
            long len;
            hid_t th = mhdf_openAdjacency( filePtr, fileInfo->elems[i].handle, &len, &status );
            if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

            rval = read_adjacencies( th, len );
            mhdf_closeData( filePtr, th, &status );
            mpe_event.end( rval );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }

    if( cputime ) _times[ADJACENCY_TIME] = timer->time_elapsed();

    // If doing ELEMENTS=SIDES then we need to delete any entities
    // that we read that aren't actually sides (e.g. an interior face
    // that connects two disjoint portions of the part). Both
    // update_connectivity and reading of any explicit adjacencies must
    // happen before this.
    if( side_mode == SM_SIDES )
    {
        debug_barrier();
        mpe_event.start( "cleaning up non-side lower-dim elements" );
        dbgOut.tprint( 1, "CHECKING FOR AND DELETING NON-SIDE ELEMENTS\n" );
        rval = delete_non_side_elements( side_entities );
        mpe_event.end( rval );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[DELETE_NON_SIDEELEM_TIME] = timer->time_elapsed();

    debug_barrier();
    dbgOut.tprint( 1, "READING SETS\n" );

    // If reading contained/child sets but not their contents then find
    // them now. If we were also reading their contents we would
    // have found them already.
    if( content_mode == RSM_SETS || child_mode == RSM_SETS )
    {
        dbgOut.tprint( 1, "  doing read_set_ids_recursive\n" );
        mpe_event.start( "finding recursively contained sets" );
        rval = read_set_ids_recursive( sets, content_mode == RSM_SETS, child_mode == RSM_SETS );
        mpe_event.end( rval );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[READ_SET_IDS_RECURS_TIME] = timer->time_elapsed();

    dbgOut.tprint( 1, "  doing find_sets_containing\n" );
    mpe_event.start( "finding sets containing any read entities" );

    // Decide whether to read set-containing parents
    bool read_set_containing_parents = true;
    std::string tmp_opt;
    rval = opts.get_option( "NO_SET_CONTAINING_PARENTS", tmp_opt );
    if( MB_SUCCESS == rval ) read_set_containing_parents = false;

    // Append file IDs of sets containing any of the nodes or elements
    // we've read up to this point.
    rval = find_sets_containing( sets, read_set_containing_parents );
    mpe_event.end( rval );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[FIND_SETS_CONTAINING_TIME] = timer->time_elapsed();

    // Now actually read all set data and instantiate sets in MOAB.
    // Get any contained sets out of file_ids.
    mpe_event.start( "reading set contents/parents/children" );
    EntityHandle first_set = fileInfo->sets.start_id;
    sets.merge( file_ids.lower_bound( first_set ), file_ids.lower_bound( first_set + fileInfo->sets.count ) );
    dbgOut.tprint( 1, "  doing read_sets\n" );
    rval = read_sets( sets );
    mpe_event.end( rval );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cputime ) _times[READ_SETS_TIME] = timer->time_elapsed();

    dbgOut.tprint( 1, "READING TAGS\n" );

    for( int i = 0; i < fileInfo->num_tag_desc; ++i )
    {
        mpe_event.start( "reading tag: ", fileInfo->tags[i].name );
        rval = read_tag( i );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( cputime ) _times[READ_TAGS_TIME] = timer->time_elapsed();

    dbgOut.tprint( 1, "PARTIAL READ COMPLETE.\n" );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::search_tag_values( int tag_index, const std::vector< int >& sorted_values, Range& file_ids,
                                       bool sets_only )
{
    ErrorCode rval;
    mhdf_Status status;
    std::vector< EntityHandle >::iterator iter;
    const mhdf_TagDesc& tag = fileInfo->tags[tag_index];
    long size;
    long start_id;

    CHECK_OPEN_HANDLES;

    debug_barrier();

    // Do dense data

    hid_t table;
    const char* name;
    std::vector< EntityHandle > indices;
    // These are probably in order of dimension, so iterate
    // in reverse order to make Range insertions more efficient.
    std::vector< int > grp_indices( tag.dense_elem_indices, tag.dense_elem_indices + tag.num_dense_indices );
    for( std::vector< int >::reverse_iterator i = grp_indices.rbegin(); i != grp_indices.rend(); ++i )
    {
        int idx = *i;
        if( idx == -2 )
        {
            name     = mhdf_set_type_handle();
            start_id = fileInfo->sets.start_id;
        }
        else if( sets_only )
        {
            continue;
        }
        else if( idx == -1 )
        {
            name     = mhdf_node_type_handle();
            start_id = fileInfo->nodes.start_id;
        }
        else
        {
            if( idx < 0 || idx >= fileInfo->num_elem_desc ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
            name     = fileInfo->elems[idx].handle;
            start_id = fileInfo->elems[idx].desc.start_id;
        }
        table = mhdf_openDenseTagData( filePtr, tag.name, name, &size, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        rval = search_tag_values( table, size, sorted_values, indices );
        mhdf_closeData( filePtr, table, &status );
        if( MB_SUCCESS != rval || is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        // Convert from table indices to file IDs and add to result list
        std::sort( indices.begin(), indices.end(), std::greater< EntityHandle >() );
        std::transform( indices.begin(), indices.end(), range_inserter( file_ids ),
                        // std::bind1st(std::plus<long>(), start_id));
                        std::bind( std::plus< long >(), start_id, std::placeholders::_1 ) );
        indices.clear();
    }

    if( !tag.have_sparse ) return MB_SUCCESS;

    // Do sparse data

    hid_t tables[2];
    long junk;  // Redundant value for non-variable-length tags
    mhdf_openSparseTagData( filePtr, tag.name, &size, &junk, tables, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    rval = search_tag_values( tables[1], size, sorted_values, indices );
    mhdf_closeData( filePtr, tables[1], &status );
    if( MB_SUCCESS != rval || is_error( status ) )
    {
        mhdf_closeData( filePtr, tables[0], &status );
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }
    // Convert to ranges
    std::sort( indices.begin(), indices.end() );
    std::vector< EntityHandle > ranges;
    iter = indices.begin();
    while( iter != indices.end() )
    {
        ranges.push_back( *iter );
        EntityHandle last = *iter;
        for( ++iter; iter != indices.end() && ( last + 1 ) == *iter; ++iter, ++last )
            ;
        ranges.push_back( last );
    }
    // Read file ids
    iter                 = ranges.begin();
    unsigned long offset = 0;
    while( iter != ranges.end() )
    {
        long begin = *iter;
        ++iter;
        long end = *iter;
        ++iter;
        mhdf_readSparseTagEntitiesWithOpt( tables[0], begin, end - begin + 1, handleType, &indices[offset], indepIO,
                                           &status );
        if( is_error( status ) )
        {
            mhdf_closeData( filePtr, tables[0], &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
        offset += end - begin + 1;
    }
    mhdf_closeData( filePtr, tables[0], &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    assert( offset == indices.size() );
    std::sort( indices.begin(), indices.end() );

    if( sets_only )
    {
        iter = std::lower_bound( indices.begin(), indices.end(),
                                 ( EntityHandle )( fileInfo->sets.start_id + fileInfo->sets.count ) );
        indices.erase( iter, indices.end() );
        iter = std::lower_bound( indices.begin(), indices.end(), fileInfo->sets.start_id );
        indices.erase( indices.begin(), iter );
    }
    copy_sorted_file_ids( &indices[0], indices.size(), file_ids );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::get_tagged_entities( int tag_index, Range& file_ids )
{
    const mhdf_TagDesc& tag = fileInfo->tags[tag_index];

    CHECK_OPEN_HANDLES;

    // Do dense data
    Range::iterator hint = file_ids.begin();
    for( int i = 0; i < tag.num_dense_indices; ++i )
    {
        int idx = tag.dense_elem_indices[i];
        mhdf_EntDesc* ents;
        if( idx == -2 )
            ents = &fileInfo->sets;
        else if( idx == -1 )
            ents = &fileInfo->nodes;
        else
        {
            if( idx < 0 || idx >= fileInfo->num_elem_desc ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
            ents = &( fileInfo->elems[idx].desc );
        }

        EntityHandle h = (EntityHandle)ents->start_id;
        hint           = file_ids.insert( hint, h, h + ents->count - 1 );
    }

    if( !tag.have_sparse ) return MB_SUCCESS;

    // Do sparse data

    mhdf_Status status;
    hid_t tables[2];
    long size, junk;
    mhdf_openSparseTagData( filePtr, tag.name, &size, &junk, tables, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    mhdf_closeData( filePtr, tables[1], &status );
    if( is_error( status ) )
    {
        mhdf_closeData( filePtr, tables[0], &status );
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    hid_t file_type = H5Dget_type( tables[0] );
    if( file_type < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    hint                   = file_ids.begin();
    EntityHandle* buffer   = reinterpret_cast< EntityHandle* >( dataBuffer );
    const long buffer_size = bufferSize / std::max( sizeof( EntityHandle ), H5Tget_size( file_type ) );
    long remaining = size, offset = 0;
    while( remaining )
    {
        long count = std::min( buffer_size, remaining );
        assert_range( buffer, count );
        mhdf_readSparseTagEntitiesWithOpt( *tables, offset, count, file_type, buffer, collIO, &status );
        if( is_error( status ) )
        {
            H5Tclose( file_type );
            mhdf_closeData( filePtr, *tables, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
        H5Tconvert( file_type, handleType, count, buffer, NULL, H5P_DEFAULT );

        std::sort( buffer, buffer + count );
        for( long i = 0; i < count; ++i )
            hint = file_ids.insert( hint, buffer[i], buffer[i] );

        remaining -= count;
        offset += count;
    }

    H5Tclose( file_type );
    mhdf_closeData( filePtr, *tables, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::search_tag_values( hid_t tag_table, unsigned long table_size,
                                       const std::vector< int >& sorted_values,
                                       std::vector< EntityHandle >& value_indices )
{
    debug_barrier();

    CHECK_OPEN_HANDLES;

    mhdf_Status status;
    size_t chunk_size = bufferSize / sizeof( int );
    int* buffer       = reinterpret_cast< int* >( dataBuffer );
    size_t remaining = table_size, offset = 0;
    while( remaining )
    {
        // Get a block of tag values
        size_t count = std::min( chunk_size, remaining );
        assert_range( buffer, count );
        mhdf_readTagValuesWithOpt( tag_table, offset, count, H5T_NATIVE_INT, buffer, collIO, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        // Search tag values
        for( size_t i = 0; i < count; ++i )
            if( std::binary_search( sorted_values.begin(), sorted_values.end(), (int)buffer[i] ) )
                value_indices.push_back( i + offset );

        offset += count;
        remaining -= count;
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_nodes( const Range& node_file_ids )
{
    ErrorCode rval;
    mhdf_Status status;
    const int dim = fileInfo->nodes.vals_per_ent;
    Range range;

    CHECK_OPEN_HANDLES;

    if( node_file_ids.empty() && !nativeParallel ) return MB_SUCCESS;

    int cdim;
    rval = iFace->get_dimension( cdim );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( cdim < dim )
    {
        rval = iFace->set_dimension( dim );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    hid_t data_id = mhdf_openNodeCoordsSimple( filePtr, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    EntityHandle handle;
    std::vector< double* > arrays( dim );
    const size_t num_nodes = node_file_ids.size();
    if( num_nodes > 0 )
    {
        rval = readUtil->get_node_coords( dim, (int)num_nodes, 0, handle, arrays );
        if( MB_SUCCESS != rval )
        {
            mhdf_closeData( filePtr, data_id, &status );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }

    if( blockedCoordinateIO )
    {
        try
        {
            for( int d = 0; d < dim; ++d )
            {
                ReadHDF5Dataset reader( "blocked coords", data_id, nativeParallel, mpiComm, false );
                reader.set_column( d );
                reader.set_file_ids( node_file_ids, fileInfo->nodes.start_id, num_nodes, H5T_NATIVE_DOUBLE );
                dbgOut.printf( 3, "Reading %lu chunks for coordinate dimension %d\n", reader.get_read_count(), d );
                // Should normally only have one read call, unless sparse nature
                // of file_ids caused reader to do something strange
                size_t count, offset = 0;
                int nn = 0;
                while( !reader.done() )
                {
                    dbgOut.printf( 3, "Reading chunk %d for dimension %d\n", ++nn, d );
                    reader.read( arrays[d] + offset, count );
                    offset += count;
                }
                if( offset != num_nodes )
                {
                    mhdf_closeData( filePtr, data_id, &status );
                    assert( false );
                    return MB_FAILURE;
                }
            }
        }
        catch( ReadHDF5Dataset::Exception )
        {
            mhdf_closeData( filePtr, data_id, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }
    else
    {  // !blockedCoordinateIO
        double* buffer  = (double*)dataBuffer;
        long chunk_size = bufferSize / ( 3 * sizeof( double ) );
        long coffset    = 0;
        int nn          = 0;
        try
        {
            ReadHDF5Dataset reader( "interleaved coords", data_id, nativeParallel, mpiComm, false );
            reader.set_file_ids( node_file_ids, fileInfo->nodes.start_id, chunk_size, H5T_NATIVE_DOUBLE );
            dbgOut.printf( 3, "Reading %lu chunks for coordinate coordinates\n", reader.get_read_count() );
            while( !reader.done() )
            {
                dbgOut.tprintf( 3, "Reading chunk %d of node coords\n", ++nn );

                size_t count;
                reader.read( buffer, count );

                for( size_t i = 0; i < count; ++i )
                    for( int d = 0; d < dim; ++d )
                        arrays[d][coffset + i] = buffer[dim * i + d];
                coffset += count;
            }
        }
        catch( ReadHDF5Dataset::Exception )
        {
            mhdf_closeData( filePtr, data_id, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }

    dbgOut.print( 3, "Closing node coordinate table\n" );
    mhdf_closeData( filePtr, data_id, &status );
    for( int d = dim; d < cdim; ++d )
        memset( arrays[d], 0, num_nodes * sizeof( double ) );

    dbgOut.printf( 3, "Updating ID to handle map for %lu nodes\n", (unsigned long)node_file_ids.size() );
    return insert_in_id_map( node_file_ids, handle );
}

ErrorCode ReadHDF5::read_elems( int i )
{
    Range ids;
    ids.insert( fileInfo->elems[i].desc.start_id,
                fileInfo->elems[i].desc.start_id + fileInfo->elems[i].desc.count - 1 );
    return read_elems( i, ids );
}

ErrorCode ReadHDF5::read_elems( int i, const Range& file_ids, Range* node_ids )
{
    if( fileInfo->elems[i].desc.vals_per_ent < 0 )
    {
        if( node_ids != 0 )  // Not implemented for version 3 format of poly data
            MB_CHK_ERR( MB_TYPE_OUT_OF_RANGE );
        return read_poly( fileInfo->elems[i], file_ids );
    }
    else
        return read_elems( fileInfo->elems[i], file_ids, node_ids );
}

ErrorCode ReadHDF5::read_elems( const mhdf_ElemDesc& elems, const Range& file_ids, Range* node_ids )
{
    CHECK_OPEN_HANDLES;

    debug_barrier();
    dbgOut.tprintf( 1, "READING %s CONNECTIVITY (%lu elems in %lu selects)\n", elems.handle,
                    (unsigned long)file_ids.size(), (unsigned long)file_ids.psize() );

    ErrorCode rval = MB_SUCCESS;
    mhdf_Status status;

    EntityType type = CN::EntityTypeFromName( elems.type );
    if( type == MBMAXTYPE ) { MB_SET_ERR( MB_FAILURE, "Unknown element type: \"" << elems.type << "\"" ); }

    const int nodes_per_elem = elems.desc.vals_per_ent;
    const size_t count       = file_ids.size();
    hid_t data_id            = mhdf_openConnectivitySimple( filePtr, elems.handle, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    EntityHandle handle;
    EntityHandle* array = 0;
    if( count > 0 ) rval = readUtil->get_element_connect( count, nodes_per_elem, type, 0, handle, array );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    try
    {
        EntityHandle* buffer     = reinterpret_cast< EntityHandle* >( dataBuffer );
        const size_t buffer_size = bufferSize / ( sizeof( EntityHandle ) * nodes_per_elem );
        ReadHDF5Dataset reader( elems.handle, data_id, nativeParallel, mpiComm );
        reader.set_file_ids( file_ids, elems.desc.start_id, buffer_size, handleType );
        dbgOut.printf( 3, "Reading connectivity in %lu chunks for element group \"%s\"\n", reader.get_read_count(),
                       elems.handle );
        EntityHandle* iter = array;
        int nn             = 0;
        while( !reader.done() )
        {
            dbgOut.printf( 3, "Reading chunk %d for \"%s\"\n", ++nn, elems.handle );

            size_t num_read;
            reader.read( buffer, num_read );
            iter = std::copy( buffer, buffer + num_read * nodes_per_elem, iter );

            if( node_ids )
            {
                std::sort( buffer, buffer + num_read * nodes_per_elem );
                num_read = std::unique( buffer, buffer + num_read * nodes_per_elem ) - buffer;
                copy_sorted_file_ids( buffer, num_read, *node_ids );
            }
        }
        assert( iter - array == (ptrdiff_t)count * nodes_per_elem );
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    if( !node_ids )
    {
        rval = convert_id_to_handle( array, count * nodes_per_elem );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

        rval = readUtil->update_adjacencies( handle, count, nodes_per_elem, array );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }
    else
    {
        IDConnectivity t;
        t.handle         = handle;
        t.count          = count;
        t.nodes_per_elem = nodes_per_elem;
        t.array          = array;
        idConnectivityList.push_back( t );
    }

    return insert_in_id_map( file_ids, handle );
}

ErrorCode ReadHDF5::update_connectivity()
{
    ErrorCode rval;
    std::vector< IDConnectivity >::iterator i;
    for( i = idConnectivityList.begin(); i != idConnectivityList.end(); ++i )
    {
        rval = convert_id_to_handle( i->array, i->count * i->nodes_per_elem );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

        rval = readUtil->update_adjacencies( i->handle, i->count, i->nodes_per_elem, i->array );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }
    idConnectivityList.clear();

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_node_adj_elems( const mhdf_ElemDesc& group, Range* handles_out )
{
    mhdf_Status status;
    ErrorCode rval;

    CHECK_OPEN_HANDLES;

    hid_t table = mhdf_openConnectivitySimple( filePtr, group.handle, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    rval = read_node_adj_elems( group, table, handles_out );

    mhdf_closeData( filePtr, table, &status );
    if( MB_SUCCESS == rval && is_error( status ) ) MB_SET_ERR_RET_VAL( "ReadHDF5 Failure", MB_FAILURE );

    return rval;
}

ErrorCode ReadHDF5::read_node_adj_elems( const mhdf_ElemDesc& group, hid_t table_handle, Range* handles_out )
{
    CHECK_OPEN_HANDLES;

    debug_barrier();

    mhdf_Status status;
    ErrorCode rval;
    IODebugTrack debug_track( debugTrack, std::string( group.handle ) );

    // Copy data to local variables (makes other code clearer)
    const int node_per_elem = group.desc.vals_per_ent;
    long start_id           = group.desc.start_id;
    long remaining          = group.desc.count;
    const EntityType type   = CN::EntityTypeFromName( group.type );

    // Figure out how many elements we can read in each pass
    long* const buffer     = reinterpret_cast< long* >( dataBuffer );
    const long buffer_size = bufferSize / ( node_per_elem * sizeof( buffer[0] ) );
    // Read all element connectivity in buffer_size blocks
    long offset = 0;
    dbgOut.printf( 3, "Reading node-adjacent elements from \"%s\" in %ld chunks\n", group.handle,
                   ( remaining + buffer_size - 1 ) / buffer_size );
    int nn = 0;
    Range::iterator hint;
    if( handles_out ) hint = handles_out->begin();
    while( remaining )
    {
        dbgOut.printf( 3, "Reading chunk %d of connectivity data for \"%s\"\n", ++nn, group.handle );

        // Read a block of connectivity data
        const long count = std::min( remaining, buffer_size );
        debug_track.record_io( offset, count );
        assert_range( buffer, count * node_per_elem );
        mhdf_readConnectivityWithOpt( table_handle, offset, count, H5T_NATIVE_LONG, buffer, collIO, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        offset += count;
        remaining -= count;

        // Count the number of elements in the block that we want,
        // zero connectivity for other elements
        long num_elem = 0;
        long* iter    = buffer;
        for( long i = 0; i < count; ++i )
        {
            for( int j = 0; j < node_per_elem; ++j )
            {
                iter[j] = (long)idMap.find( iter[j] );
                if( !iter[j] )
                {
                    iter[0] = 0;
                    break;
                }
            }
            if( iter[0] ) ++num_elem;
            iter += node_per_elem;
        }

        if( !num_elem )
        {
            start_id += count;
            continue;
        }

        // Create elements
        EntityHandle handle;
        EntityHandle* array;
        rval = readUtil->get_element_connect( (int)num_elem, node_per_elem, type, 0, handle, array );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

        // Copy all non-zero connectivity values
        iter                = buffer;
        EntityHandle* iter2 = array;
        EntityHandle h      = handle;
        for( long i = 0; i < count; ++i )
        {
            if( !*iter )
            {
                iter += node_per_elem;
                continue;
            }
            if( !idMap.insert( start_id + i, h++, 1 ).second ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

            long* const end = iter + node_per_elem;
            for( ; iter != end; ++iter, ++iter2 )
                *iter2 = (EntityHandle)*iter;
        }
        assert( iter2 - array == num_elem * node_per_elem );
        start_id += count;

        rval = readUtil->update_adjacencies( handle, num_elem, node_per_elem, array );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        if( handles_out ) hint = handles_out->insert( hint, handle, handle + num_elem - 1 );
    }

    debug_track.all_reduce();
    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_elems( int i, const Range& elems_in, Range& nodes )
{
    CHECK_OPEN_HANDLES;

    debug_barrier();
    dbgOut.tprintf( 1, "READING %s CONNECTIVITY (%lu elems in %lu selects)\n", fileInfo->elems[i].handle,
                    (unsigned long)elems_in.size(), (unsigned long)elems_in.psize() );

    EntityHandle* const buffer = reinterpret_cast< EntityHandle* >( dataBuffer );
    const int node_per_elem    = fileInfo->elems[i].desc.vals_per_ent;
    const size_t buffer_size   = bufferSize / ( node_per_elem * sizeof( EntityHandle ) );

    if( elems_in.empty() ) return MB_SUCCESS;

    assert( (long)elems_in.front() >= fileInfo->elems[i].desc.start_id );
    assert( (long)elems_in.back() - fileInfo->elems[i].desc.start_id < fileInfo->elems[i].desc.count );

    // We don't support version 3 style poly element data
    if( fileInfo->elems[i].desc.vals_per_ent <= 0 ) MB_CHK_ERR( MB_TYPE_OUT_OF_RANGE );

    mhdf_Status status;
    hid_t table = mhdf_openConnectivitySimple( filePtr, fileInfo->elems[i].handle, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    try
    {
        ReadHDF5Dataset reader( fileInfo->elems[i].handle, table, nativeParallel, mpiComm );
        reader.set_file_ids( elems_in, fileInfo->elems[i].desc.start_id, buffer_size, handleType );
        dbgOut.printf( 3, "Reading node list in %lu chunks for \"%s\"\n", reader.get_read_count(),
                       fileInfo->elems[i].handle );
        int nn = 0;
        while( !reader.done() )
        {
            dbgOut.printf( 3, "Reading chunk %d of \"%s\" connectivity\n", ++nn, fileInfo->elems[i].handle );
            size_t num_read;
            reader.read( buffer, num_read );
            std::sort( buffer, buffer + num_read * node_per_elem );
            num_read = std::unique( buffer, buffer + num_read * node_per_elem ) - buffer;
            copy_sorted_file_ids( buffer, num_read, nodes );
        }
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_poly( const mhdf_ElemDesc& elems, const Range& file_ids )
{
    class PolyReader : public ReadHDF5VarLen
    {
      private:
        const EntityType type;
        ReadHDF5* readHDF5;

      public:
        PolyReader( EntityType elem_type, void* buffer, size_t buffer_size, ReadHDF5* owner, DebugOutput& dbg )
            : ReadHDF5VarLen( dbg, buffer, buffer_size ), type( elem_type ), readHDF5( owner )
        {
        }
        virtual ~PolyReader() {}
        ErrorCode store_data( EntityHandle file_id, void* data, long len, bool )
        {
            size_t valid;
            EntityHandle* conn = reinterpret_cast< EntityHandle* >( data );
            readHDF5->convert_id_to_handle( conn, len, valid );
            if( valid != (size_t)len ) MB_CHK_ERR( MB_ENTITY_NOT_FOUND );
            EntityHandle handle;
            ErrorCode rval = readHDF5->moab()->create_element( type, conn, len, handle );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

            rval = readHDF5->insert_in_id_map( file_id, handle );
            return rval;
        }
    };

    CHECK_OPEN_HANDLES;

    debug_barrier();

    EntityType type = CN::EntityTypeFromName( elems.type );
    if( type == MBMAXTYPE ) { MB_SET_ERR( MB_FAILURE, "Unknown element type: \"" << elems.type << "\"" ); }

    hid_t handles[2];
    mhdf_Status status;
    long num_poly, num_conn, first_id;
    mhdf_openPolyConnectivity( filePtr, elems.handle, &num_poly, &num_conn, &first_id, handles, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    std::string nm( elems.handle );
    ReadHDF5Dataset offset_reader( ( nm + " offsets" ).c_str(), handles[0], nativeParallel, mpiComm, true );
    ReadHDF5Dataset connect_reader( ( nm + " data" ).c_str(), handles[1], nativeParallel, mpiComm, true );

    PolyReader tool( type, dataBuffer, bufferSize, this, dbgOut );
    return tool.read( offset_reader, connect_reader, file_ids, first_id, handleType );
}

ErrorCode ReadHDF5::delete_non_side_elements( const Range& side_ents )
{
    ErrorCode rval;

    // Build list of entities that we need to find the sides of
    Range explicit_ents;
    Range::iterator hint = explicit_ents.begin();
    for( IDMap::iterator i = idMap.begin(); i != idMap.end(); ++i )
    {
        EntityHandle start = i->value;
        EntityHandle end   = i->value + i->count - 1;
        EntityType type    = TYPE_FROM_HANDLE( start );
        assert( type == TYPE_FROM_HANDLE( end ) );  // Otherwise handle space entirely full!!
        if( type != MBVERTEX && type != MBENTITYSET ) hint = explicit_ents.insert( hint, start, end );
    }
    explicit_ents = subtract( explicit_ents, side_ents );

    // Figure out which entities we want to delete
    Range dead_ents( side_ents );
    Range::iterator ds, de, es;
    ds = dead_ents.lower_bound( CN::TypeDimensionMap[1].first );
    de = dead_ents.lower_bound( CN::TypeDimensionMap[2].first, ds );
    if( ds != de )
    {
        // Get subset of explicit ents of dimension greater than 1
        es = explicit_ents.lower_bound( CN::TypeDimensionMap[2].first );
        Range subset, adj;
        subset.insert( es, explicit_ents.end() );
        rval = iFace->get_adjacencies( subset, 1, false, adj, Interface::UNION );
        if( MB_SUCCESS != rval ) return rval;
        dead_ents = subtract( dead_ents, adj );
    }
    ds = dead_ents.lower_bound( CN::TypeDimensionMap[2].first );
    de = dead_ents.lower_bound( CN::TypeDimensionMap[3].first, ds );
    assert( de == dead_ents.end() );
    if( ds != de )
    {
        // Get subset of explicit ents of dimension 3
        es = explicit_ents.lower_bound( CN::TypeDimensionMap[3].first );
        Range subset, adj;
        subset.insert( es, explicit_ents.end() );
        rval = iFace->get_adjacencies( subset, 2, false, adj, Interface::UNION );
        if( MB_SUCCESS != rval ) return rval;
        dead_ents = subtract( dead_ents, adj );
    }

    // Now delete anything remaining in dead_ents
    dbgOut.printf( 2, "Deleting %lu elements\n", (unsigned long)dead_ents.size() );
    dbgOut.print( 4, "\tDead entities: ", dead_ents );
    rval = iFace->delete_entities( dead_ents );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    // Remove dead entities from ID map
    while( !dead_ents.empty() )
    {
        EntityHandle start = dead_ents.front();
        EntityID count     = dead_ents.const_pair_begin()->second - start + 1;
        IDMap::iterator rit;
        for( rit = idMap.begin(); rit != idMap.end(); ++rit )
            if( rit->value <= start && ( EntityID )( start - rit->value ) < rit->count ) break;
        if( rit == idMap.end() ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        EntityID offset = start - rit->value;
        EntityID avail  = rit->count - offset;
        if( avail < count ) count = avail;

        dead_ents.erase( dead_ents.begin(), dead_ents.begin() + count );
        idMap.erase( rit->begin + offset, count );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_sets( const Range& file_ids )
{
    CHECK_OPEN_HANDLES;

    debug_barrier();

    mhdf_Status status;
    ErrorCode rval;

    const size_t num_sets = fileInfo->sets.count;
    if( !num_sets )  // If no sets at all!
        return MB_SUCCESS;

    // Create sets
    std::vector< unsigned > flags( file_ids.size() );
    Range::iterator si = file_ids.begin();
    for( size_t i = 0; i < flags.size(); ++i, ++si )
        flags[i] = setMeta[*si - fileInfo->sets.start_id][3] & ~(long)mhdf_SET_RANGE_BIT;
    EntityHandle start_handle;
    // the files ids could be empty, for empty partitions
    if( !file_ids.empty() )
    {
        rval = readUtil->create_entity_sets( flags.size(), &flags[0], 0, start_handle );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        rval = insert_in_id_map( file_ids, start_handle );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    // Read contents
    if( fileInfo->have_set_contents )
    {
        long len     = 0;
        hid_t handle = mhdf_openSetData( filePtr, &len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        ReadHDF5Dataset dat( "set contents", handle, nativeParallel, mpiComm, true );
        rval = read_set_data( file_ids, start_handle, dat, CONTENT );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    // Read set child lists
    if( fileInfo->have_set_children )
    {
        long len     = 0;
        hid_t handle = mhdf_openSetChildren( filePtr, &len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        ReadHDF5Dataset dat( "set children", handle, nativeParallel, mpiComm, true );
        rval = read_set_data( file_ids, start_handle, dat, CHILD );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    // Read set parent lists
    if( fileInfo->have_set_parents )
    {
        long len     = 0;
        hid_t handle = mhdf_openSetParents( filePtr, &len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        ReadHDF5Dataset dat( "set parents", handle, nativeParallel, mpiComm, true );
        rval = read_set_data( file_ids, start_handle, dat, PARENT );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_all_set_meta()
{
    CHECK_OPEN_HANDLES;

    assert( !setMeta );
    const long num_sets = fileInfo->sets.count;
    if( !num_sets ) return MB_SUCCESS;

    mhdf_Status status;
    hid_t handle = mhdf_openSetMetaSimple( filePtr, &status );
    if( is_error( status ) ) { MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" ); }

    // Allocate extra space if we need it for data conversion
    hid_t meta_type = H5Dget_type( handle );
    size_t size     = H5Tget_size( meta_type );
    if( size > sizeof( long ) )
        setMeta = new long[( num_sets * size + ( sizeof( long ) - 1 ) ) / sizeof( long )][4];
    else
        setMeta = new long[num_sets][4];

    // Set some parameters based on whether or not each proc reads the
    // table or only the root reads it and bcasts it to the others
    int rank     = 0;
    bool bcast   = false;
    hid_t ioprop = H5P_DEFAULT;
#ifdef MOAB_HAVE_MPI
    MPI_Comm comm = 0;
    if( nativeParallel )
    {
        rank  = myPcomm->proc_config().proc_rank();
        comm  = myPcomm->proc_config().proc_comm();
        bcast = bcastDuplicateReads;
        if( !bcast ) ioprop = collIO;
    }
#endif

    if( !bcast || 0 == rank )
    {
        mhdf_readSetMetaWithOpt( handle, 0, num_sets, meta_type, setMeta, ioprop, &status );
        if( is_error( status ) )
        {
            H5Tclose( meta_type );
            mhdf_closeData( filePtr, handle, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        H5Tconvert( meta_type, H5T_NATIVE_LONG, num_sets * 4, setMeta, 0, H5P_DEFAULT );
    }
    mhdf_closeData( filePtr, handle, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    H5Tclose( meta_type );

    if( bcast )
    {
#ifdef MOAB_HAVE_MPI
        int ierr = MPI_Bcast( (void*)setMeta, num_sets * 4, MPI_LONG, 0, comm );
        if( MPI_SUCCESS != ierr ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
#else
        assert( rank == 0 );              // If not MPI, then only one proc
#endif
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_set_ids_recursive( Range& sets_in_out, bool contained_sets, bool child_sets )
{
    CHECK_OPEN_HANDLES;
    mhdf_Status status;

    if( !fileInfo->have_set_children ) child_sets = false;
    if( !fileInfo->have_set_contents ) contained_sets = false;
    if( !child_sets && !contained_sets ) return MB_SUCCESS;

    // Open data tables
    if( fileInfo->sets.count == 0 )
    {
        assert( sets_in_out.empty() );
        return MB_SUCCESS;
    }

    if( !contained_sets && !child_sets ) return MB_SUCCESS;

    ReadHDF5Dataset cont( "set contents", false, mpiComm );
    ReadHDF5Dataset child( "set children", false, mpiComm );

    if( contained_sets )
    {
        long content_len     = 0;
        hid_t content_handle = mhdf_openSetData( filePtr, &content_len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        try
        {
            cont.init( content_handle, true );
        }
        catch( ReadHDF5Dataset::Exception )
        {
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }

    if( child_sets )
    {
        long child_len     = 0;
        hid_t child_handle = mhdf_openSetChildren( filePtr, &child_len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        try
        {
            child.init( child_handle, true );
        }
        catch( ReadHDF5Dataset::Exception )
        {
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }

    ErrorCode rval = MB_SUCCESS;
    Range children, new_children( sets_in_out );
    int iteration_count = 0;
    do
    {
        ++iteration_count;
        dbgOut.tprintf( 2, "Iteration %d of read_set_ids_recursive\n", iteration_count );
        children.clear();
        if( child_sets )
        {
            rval = read_set_data( new_children, 0, child, CHILD, &children );
            if( MB_SUCCESS != rval ) break;
        }
        if( contained_sets )
        {
            rval = read_set_data( new_children, 0, cont, CONTENT, &children );
            // Remove any non-set values
            Range::iterator it = children.lower_bound( fileInfo->sets.start_id );
            children.erase( children.begin(), it );
            it = children.lower_bound( fileInfo->sets.start_id + fileInfo->sets.count );
            children.erase( it, children.end() );
            if( MB_SUCCESS != rval ) break;
        }
        new_children = subtract( children, sets_in_out );
        dbgOut.print_ints( 2, "Adding additional contained/child sets", new_children );
        sets_in_out.merge( new_children );
    } while( !new_children.empty() );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::find_sets_containing( Range& sets_out, bool read_set_containing_parents )
{
    ErrorCode rval;
    mhdf_Status status;

    CHECK_OPEN_HANDLES;

    if( !fileInfo->have_set_contents ) return MB_SUCCESS;
    assert( fileInfo->sets.count );

    // Open data tables
    long content_len     = 0;
    hid_t content_handle = mhdf_openSetData( filePtr, &content_len, &status );
    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    hid_t data_type = H5Dget_type( content_handle );

    rval = find_sets_containing( content_handle, data_type, content_len, read_set_containing_parents, sets_out );

    H5Tclose( data_type );

    mhdf_closeData( filePtr, content_handle, &status );
    if( MB_SUCCESS == rval && is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    return rval;
}

static bool set_map_intersect( bool ranged, const long* contents, int content_len,
                               const RangeMap< long, EntityHandle >& id_map )
{
    if( ranged )
    {
        if( !content_len || id_map.empty() ) return false;

        const long* j         = contents;
        const long* const end = contents + content_len;
        assert( content_len % 2 == 0 );
        while( j != end )
        {
            long start = *( j++ );
            long count = *( j++ );
            if( id_map.intersects( start, count ) ) return true;
        }
    }
    else
    {
        const long* const end = contents + content_len;
        for( const long* i = contents; i != end; ++i )
            if( id_map.exists( *i ) ) return true;
    }

    return false;
}

struct SetContOffComp
{
    bool operator()( const long a1[4], const long a2[4] )
    {
        return a1[ReadHDF5::CONTENT] < a2[0];
    }
};

ErrorCode ReadHDF5::find_sets_containing( hid_t contents_handle, hid_t content_type, long contents_len,
                                          bool read_set_containing_parents, Range& file_ids )
{
    CHECK_OPEN_HANDLES;

    // Scan all set contents data

    const size_t content_size = H5Tget_size( content_type );
    const long num_sets       = fileInfo->sets.count;
    dbgOut.printf( 2, "Searching contents of %ld\n", num_sets );
    mhdf_Status status;

    int rank   = 0;
    bool bcast = false;
#ifdef MOAB_HAVE_MPI
    MPI_Comm comm = 0;
    if( nativeParallel )
    {
        rank  = myPcomm->proc_config().proc_rank();
        comm  = myPcomm->proc_config().proc_comm();
        bcast = bcastDuplicateReads;
    }
#endif

    // Check offsets so that we don't read past end of table or
    // walk off end of array.
    long prev = -1;
    for( long i = 0; i < num_sets; ++i )
    {
        if( setMeta[i][CONTENT] < prev )
        {
            std::cerr << "Invalid data in set contents offsets at position " << i << ": index " << setMeta[i][CONTENT]
                      << " is less than previous index " << prev << std::endl;
            std::cerr.flush();
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
        prev = setMeta[i][CONTENT];
    }
    if( setMeta[num_sets - 1][CONTENT] >= contents_len )
    {
        std::cerr << "Maximum set content index " << setMeta[num_sets - 1][CONTENT]
                  << " exceeds contents table length of " << contents_len << std::endl;
        std::cerr.flush();
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    // Set up buffer for reading set contents
    long* const content_buffer = (long*)dataBuffer;
    const long content_len     = bufferSize / std::max( content_size, sizeof( long ) );

    // Scan set table
    Range::iterator hint = file_ids.begin();
    Range tmp_range;
    long prev_idx    = -1;
    int mm           = 0;
    long sets_offset = 0;
    long temp_content[4];
    while( sets_offset < num_sets )
    {
        temp_content[0] = content_len + prev_idx;
        long sets_count =
            std::lower_bound( setMeta + sets_offset, setMeta + num_sets, temp_content, SetContOffComp() ) - setMeta -
            sets_offset;
        assert( sets_count >= 0 && sets_offset + sets_count <= num_sets );
        if( !sets_count )
        {  // Contents of single set don't fit in buffer
            long content_remaining = setMeta[sets_offset][CONTENT] - prev_idx;
            long content_offset    = prev_idx + 1;
            while( content_remaining )
            {
                long content_count = content_len < content_remaining ? 2 * ( content_len / 2 ) : content_remaining;
                assert_range( content_buffer, content_count );
                dbgOut.printf( 3, "Reading chunk %d (%ld values) from set contents table\n", ++mm, content_count );
                if( !bcast || 0 == rank )
                {
                    if( !bcast )
                        mhdf_readSetDataWithOpt( contents_handle, content_offset, content_count, content_type,
                                                 content_buffer, collIO, &status );
                    else
                        mhdf_readSetData( contents_handle, content_offset, content_count, content_type, content_buffer,
                                          &status );
                    if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

                    H5Tconvert( content_type, H5T_NATIVE_LONG, content_count, content_buffer, 0, H5P_DEFAULT );
                }
                if( bcast )
                {
#ifdef MOAB_HAVE_MPI
                    int ierr = MPI_Bcast( content_buffer, content_count, MPI_LONG, 0, comm );
                    if( MPI_SUCCESS != ierr ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
#else
                    assert( rank == 0 );  // If not MPI, then only one proc
#endif
                }

                if( read_set_containing_parents )
                {
                    tmp_range.clear();
                    if( setMeta[sets_offset][3] & mhdf_SET_RANGE_BIT )
                        tmp_range.insert( *content_buffer, *( content_buffer + 1 ) );
                    else
                        std::copy( content_buffer, content_buffer + content_count, range_inserter( tmp_range ) );
                    tmp_range = intersect( tmp_range, file_ids );
                }

                if( !tmp_range.empty() || set_map_intersect( setMeta[sets_offset][3] & mhdf_SET_RANGE_BIT,
                                                             content_buffer, content_count, idMap ) )
                {
                    long id = fileInfo->sets.start_id + sets_offset;
                    hint    = file_ids.insert( hint, id, id );
                    if( !nativeParallel )  // Don't stop if doing READ_PART because we need to read
                                           // collectively
                        break;
                }
                content_remaining -= content_count;
                content_offset += content_count;
            }
            prev_idx   = setMeta[sets_offset][CONTENT];
            sets_count = 1;
        }
        else if( long read_num = setMeta[sets_offset + sets_count - 1][CONTENT] - prev_idx )
        {
            assert( sets_count > 0 );
            assert_range( content_buffer, read_num );
            dbgOut.printf( 3, "Reading chunk %d (%ld values) from set contents table\n", ++mm, read_num );
            if( !bcast || 0 == rank )
            {
                if( !bcast )
                    mhdf_readSetDataWithOpt( contents_handle, prev_idx + 1, read_num, content_type, content_buffer,
                                             collIO, &status );
                else
                    mhdf_readSetData( contents_handle, prev_idx + 1, read_num, content_type, content_buffer, &status );
                if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

                H5Tconvert( content_type, H5T_NATIVE_LONG, read_num, content_buffer, 0, H5P_DEFAULT );
            }
            if( bcast )
            {
#ifdef MOAB_HAVE_MPI
                int ierr = MPI_Bcast( content_buffer, read_num, MPI_LONG, 0, comm );
                if( MPI_SUCCESS != ierr ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
#else
                assert( rank == 0 );      // If not MPI, then only one proc
#endif
            }

            long* buff_iter = content_buffer;
            for( long i = 0; i < sets_count; ++i )
            {
                long set_size = setMeta[i + sets_offset][CONTENT] - prev_idx;
                prev_idx += set_size;

                // Check whether contents include set already being loaded
                if( read_set_containing_parents )
                {
                    tmp_range.clear();
                    if( setMeta[sets_offset + i][3] & mhdf_SET_RANGE_BIT )
                    {
                        // put in tmp_range the contents on the set
                        // file_ids contain at this points only other sets
                        const long* j         = buff_iter;
                        const long* const end = buff_iter + set_size;
                        assert( set_size % 2 == 0 );
                        while( j != end )
                        {
                            long start = *( j++ );
                            long count = *( j++ );
                            tmp_range.insert( start, start + count - 1 );
                        }
                    }
                    else
                        std::copy( buff_iter, buff_iter + set_size, range_inserter( tmp_range ) );
                    tmp_range = intersect( tmp_range, file_ids );
                }

                if( !tmp_range.empty() ||
                    set_map_intersect( setMeta[sets_offset + i][3] & mhdf_SET_RANGE_BIT, buff_iter, set_size, idMap ) )
                {
                    long id = fileInfo->sets.start_id + sets_offset + i;
                    hint    = file_ids.insert( hint, id, id );
                }
                buff_iter += set_size;
            }
        }

        sets_offset += sets_count;
    }

    return MB_SUCCESS;
}

static Range::iterator copy_set_contents( Range::iterator hint, int ranged, EntityHandle* contents, long length,
                                          Range& results )
{
    if( ranged )
    {
        assert( length % 2 == 0 );
        for( long i = 0; i < length; i += 2 )
            hint = results.insert( hint, contents[i], contents[i] + contents[i + 1] - 1 );
    }
    else
    {
        std::sort( contents, contents + length );
        for( long i = 0; i < length; ++i )
            hint = results.insert( hint, contents[i] );
    }
    return hint;
}

ErrorCode ReadHDF5::read_set_data( const Range& set_file_ids, EntityHandle start_handle, ReadHDF5Dataset& data,
                                   SetMode mode, Range* file_ids_out )
{
    ErrorCode rval;
    Range::const_pair_iterator pi;
    Range::iterator out_hint;
    if( file_ids_out ) out_hint = file_ids_out->begin();

    // Construct range of offsets into data table at which to read
    // Note: all offsets are incremented by TWEAK because Range cannot
    // store zeros.
    const long TWEAK = 1;
    Range data_offsets;
    Range::iterator hint = data_offsets.begin();
    pi                   = set_file_ids.const_pair_begin();
    if( (long)pi->first == fileInfo->sets.start_id )
    {
        long second = pi->second - fileInfo->sets.start_id;
        if( setMeta[second][mode] >= 0 ) hint = data_offsets.insert( hint, TWEAK, setMeta[second][mode] + TWEAK );
        ++pi;
    }
    for( ; pi != set_file_ids.const_pair_end(); ++pi )
    {
        long first  = pi->first - fileInfo->sets.start_id;
        long second = pi->second - fileInfo->sets.start_id;
        long idx1   = setMeta[first - 1][mode] + 1;
        long idx2   = setMeta[second][mode];
        if( idx2 >= idx1 ) hint = data_offsets.insert( hint, idx1 + TWEAK, idx2 + TWEAK );
    }
    try
    {
        data.set_file_ids( data_offsets, TWEAK, bufferSize / sizeof( EntityHandle ), handleType );
    }
    catch( ReadHDF5Dataset::Exception )
    {
        return MB_FAILURE;
    }

    // We need to increment this for each processed set because
    // the sets were created in the order of the ids in file_ids.
    EntityHandle h = start_handle;

    const long ranged_flag = ( mode == CONTENT ) ? mhdf_SET_RANGE_BIT : 0;

    std::vector< EntityHandle > partial;  // For when we read only part of the contents of a set/entity
    Range::const_iterator fileid_iter = set_file_ids.begin();
    EntityHandle* buffer              = reinterpret_cast< EntityHandle* >( dataBuffer );
    size_t count, offset;

    int nn = 0;
    /*
    #ifdef  MOAB_HAVE_MPI
      if (nativeParallel && mode==CONTENT && myPcomm->proc_config().proc_size()>1 &&
    data_offsets.empty())
      {
        MB_SET_ERR_CONT( "ReadHDF5 Failure: Attempt reading an empty dataset on proc " <<
            myPcomm->proc_config().proc_rank());
        MPI_Abort(myPcomm->proc_config().proc_comm(), 1);
      }
    #endif
    */
    if( ( 1 >= set_file_ids.size() ) && ( data.done() ) && moab::ReadHDF5::CONTENT == mode )
        // do at least one null read, it is needed in parallel
        data.null_read();

    while( !data.done() )
    {
        dbgOut.printf( 3, "Reading chunk %d of %s\n", ++nn, data.get_debug_desc() );
        try
        {
            data.read( buffer, count );
        }
        catch( ReadHDF5Dataset::Exception )
        {
            return MB_FAILURE;
        }

        // Assert not appropriate here - I might have treated all my file ids, but maybe
        // another proc hasn't; for me, count will be zero, so I won't do anything, but
        // I still need to go through the motions to make the read work

        // Handle 'special' case where we read some, but not all
        // of the data for an entity during the last iteration.
        offset = 0;
        if( !partial.empty() )
        {  // Didn't read all of previous entity
            assert( fileid_iter != set_file_ids.end() );
            size_t num_prev = partial.size();
            size_t idx      = *fileid_iter - fileInfo->sets.start_id;
            size_t len      = idx ? setMeta[idx][mode] - setMeta[idx - 1][mode] : setMeta[idx][mode] + 1;
            offset          = len - num_prev;
            if( offset > count )
            {  // Still don't have all
                partial.insert( partial.end(), buffer, buffer + count );
                continue;
            }

            partial.insert( partial.end(), buffer, buffer + offset );
            if( file_ids_out )
            {
                out_hint = copy_set_contents( out_hint, setMeta[idx][3] & ranged_flag, &partial[0], partial.size(),
                                              *file_ids_out );
            }
            else
            {
                switch( mode )
                {
                    size_t valid;
                    case CONTENT:
                        if( setMeta[idx][3] & ranged_flag )
                        {
                            if( len % 2 ) MB_CHK_ERR( MB_INDEX_OUT_OF_RANGE );
                            Range range;
                            convert_range_to_handle( &partial[0], len / 2, range );
                            rval = moab()->add_entities( h, range );
                        }
                        else
                        {
                            convert_id_to_handle( &partial[0], len, valid );
                            rval = moab()->add_entities( h, &partial[0], valid );
                        }
                        break;
                    case CHILD:
                        convert_id_to_handle( &partial[0], len, valid );
                        rval = moab()->add_child_meshsets( h, &partial[0], valid );
                        break;
                    case PARENT:
                        convert_id_to_handle( &partial[0], len, valid );
                        rval = moab()->add_parent_meshsets( h, &partial[0], valid );
                        break;
                    default:
                        break;
                }
                if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
            }

            ++fileid_iter;
            ++h;
            partial.clear();
        }

        // Process contents for all entities for which we
        // have read the complete list
        while( offset < count )
        {
            assert( fileid_iter != set_file_ids.end() );
            size_t idx = *fileid_iter - fileInfo->sets.start_id;
            size_t len = idx ? setMeta[idx][mode] - setMeta[idx - 1][mode] : setMeta[idx][mode] + 1;
            // If we did not read all of the final entity,
            // store what we did read to be processed in the
            // next iteration
            if( offset + len > count )
            {
                partial.insert( partial.end(), buffer + offset, buffer + count );
                break;
            }

            if( file_ids_out )
            {
                out_hint =
                    copy_set_contents( out_hint, setMeta[idx][3] & ranged_flag, buffer + offset, len, *file_ids_out );
            }
            else
            {
                switch( mode )
                {
                    size_t valid;
                    case CONTENT:
                        if( setMeta[idx][3] & ranged_flag )
                        {
                            if( len % 2 ) MB_CHK_ERR( MB_INDEX_OUT_OF_RANGE );
                            Range range;
                            convert_range_to_handle( buffer + offset, len / 2, range );
                            rval = moab()->add_entities( h, range );
                        }
                        else
                        {
                            convert_id_to_handle( buffer + offset, len, valid );
                            rval = moab()->add_entities( h, buffer + offset, valid );
                        }
                        break;
                    case CHILD:
                        convert_id_to_handle( buffer + offset, len, valid );
                        rval = moab()->add_child_meshsets( h, buffer + offset, valid );
                        break;
                    case PARENT:
                        convert_id_to_handle( buffer + offset, len, valid );
                        rval = moab()->add_parent_meshsets( h, buffer + offset, valid );
                        break;
                    default:
                        break;
                }
                if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
            }

            ++fileid_iter;
            ++h;
            offset += len;
        }
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::get_set_contents( const Range& sets, Range& file_ids )
{
    CHECK_OPEN_HANDLES;

    if( !fileInfo->have_set_contents ) return MB_SUCCESS;
    dbgOut.tprint( 2, "Reading set contained file IDs\n" );
    try
    {
        mhdf_Status status;
        long content_len;
        hid_t contents = mhdf_openSetData( filePtr, &content_len, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        ReadHDF5Dataset data( "set contents", contents, nativeParallel, mpiComm, true );

        return read_set_data( sets, 0, data, CONTENT, &file_ids );
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }
}

ErrorCode ReadHDF5::read_adjacencies( hid_t table, long table_len )
{
    CHECK_OPEN_HANDLES;

    ErrorCode rval;
    mhdf_Status status;

    debug_barrier();

    hid_t read_type = H5Dget_type( table );
    if( read_type < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    const bool convert = !H5Tequal( read_type, handleType );

    EntityHandle* buffer = (EntityHandle*)dataBuffer;
    size_t chunk_size    = bufferSize / H5Tget_size( read_type );
    size_t remaining     = table_len;
    size_t left_over     = 0;
    size_t offset        = 0;
    dbgOut.printf( 3, "Reading adjacency list in %lu chunks\n",
                   (unsigned long)( remaining + chunk_size - 1 ) / chunk_size );
    int nn = 0;
    while( remaining )
    {
        dbgOut.printf( 3, "Reading chunk %d of adjacency list\n", ++nn );

        size_t count = std::min( chunk_size, remaining );
        count -= left_over;
        remaining -= count;

        assert_range( buffer + left_over, count );
        mhdf_readAdjacencyWithOpt( table, offset, count, read_type, buffer + left_over, collIO, &status );
        if( is_error( status ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

        if( convert )
        {
            herr_t err = H5Tconvert( read_type, handleType, count, buffer + left_over, 0, H5P_DEFAULT );
            if( err < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        EntityHandle* iter = buffer;
        EntityHandle* end  = buffer + count + left_over;
        while( end - iter >= 3 )
        {
            EntityHandle h      = idMap.find( *iter++ );
            EntityHandle count2 = *iter++;
            if( !h )
            {
                iter += count2;
                continue;
            }

            if( count2 < 1 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

            if( end < count2 + iter )
            {
                iter -= 2;
                break;
            }

            size_t valid;
            convert_id_to_handle( iter, count2, valid, idMap );
            rval = iFace->add_adjacencies( h, iter, valid, false );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

            iter += count2;
        }

        left_over = end - iter;
        assert_range( (char*)buffer, left_over );
        assert_range( (char*)iter, left_over );
        memmove( buffer, iter, left_over );
    }

    assert( !left_over );  // Unexpected truncation of data

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_tag( int tag_index )
{
    CHECK_OPEN_HANDLES;

    dbgOut.tprintf( 2, "Reading tag \"%s\"\n", fileInfo->tags[tag_index].name );

    debug_barrier();

    ErrorCode rval;
    mhdf_Status status;
    Tag tag         = 0;
    hid_t read_type = -1;
    bool table_type;
    rval = create_tag( fileInfo->tags[tag_index], tag, read_type );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    if( fileInfo->tags[tag_index].have_sparse )
    {
        hid_t handles[3];
        long num_ent, num_val;
        mhdf_openSparseTagData( filePtr, fileInfo->tags[tag_index].name, &num_ent, &num_val, handles, &status );
        if( is_error( status ) )
        {
            if( read_type ) H5Tclose( read_type );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        table_type = false;
        if( read_type == 0 )
        {
            read_type = H5Dget_type( handles[1] );
            if( read_type == 0 )
            {
                mhdf_closeData( filePtr, handles[0], &status );
                mhdf_closeData( filePtr, handles[0], &status );
                if( fileInfo->tags[tag_index].size <= 0 ) mhdf_closeData( filePtr, handles[2], &status );
                MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
            }
            table_type = true;
        }

        if( fileInfo->tags[tag_index].size > 0 )
        {
            dbgOut.printf( 2, "Reading sparse data for tag \"%s\"\n", fileInfo->tags[tag_index].name );
            rval = read_sparse_tag( tag, read_type, handles[0], handles[1], num_ent );
        }
        else
        {
            dbgOut.printf( 2, "Reading var-len sparse data for tag \"%s\"\n", fileInfo->tags[tag_index].name );
            rval = read_var_len_tag( tag, read_type, handles[0], handles[1], handles[2], num_ent, num_val );
        }

        if( table_type )
        {
            H5Tclose( read_type );
            read_type = 0;
        }

        mhdf_closeData( filePtr, handles[0], &status );
        if( MB_SUCCESS == rval && is_error( status ) ) rval = MB_FAILURE;
        mhdf_closeData( filePtr, handles[1], &status );
        if( MB_SUCCESS == rval && is_error( status ) ) rval = MB_FAILURE;
        if( fileInfo->tags[tag_index].size <= 0 )
        {
            mhdf_closeData( filePtr, handles[2], &status );
            if( MB_SUCCESS == rval && is_error( status ) ) rval = MB_FAILURE;
        }
        if( MB_SUCCESS != rval )
        {
            if( read_type ) H5Tclose( read_type );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }

    for( int j = 0; j < fileInfo->tags[tag_index].num_dense_indices; ++j )
    {
        long count;
        const char* name = 0;
        mhdf_EntDesc* desc;
        int elem_idx = fileInfo->tags[tag_index].dense_elem_indices[j];
        if( elem_idx == -2 )
        {
            desc = &fileInfo->sets;
            name = mhdf_set_type_handle();
        }
        else if( elem_idx == -1 )
        {
            desc = &fileInfo->nodes;
            name = mhdf_node_type_handle();
        }
        else if( elem_idx >= 0 && elem_idx < fileInfo->num_elem_desc )
        {
            desc = &fileInfo->elems[elem_idx].desc;
            name = fileInfo->elems[elem_idx].handle;
        }
        else
        {
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        dbgOut.printf( 2, "Read dense data block for tag \"%s\" on \"%s\"\n", fileInfo->tags[tag_index].name, name );

        hid_t handle = mhdf_openDenseTagData( filePtr, fileInfo->tags[tag_index].name, name, &count, &status );
        if( is_error( status ) )
        {
            rval = MB_FAILURE;  // rval = error(MB_FAILURE);
            break;
        }

        if( count > desc->count )
        {
            mhdf_closeData( filePtr, handle, &status );
            MB_SET_ERR( MB_FAILURE,
                        "Invalid data length for dense tag data: " << name << "/" << fileInfo->tags[tag_index].name );
        }

        table_type = false;
        if( read_type == 0 )
        {
            read_type = H5Dget_type( handle );
            if( read_type == 0 )
            {
                mhdf_closeData( filePtr, handle, &status );
                MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
            }
            table_type = true;
        }

        rval = read_dense_tag( tag, name, read_type, handle, desc->start_id, count );

        if( table_type )
        {
            H5Tclose( read_type );
            read_type = 0;
        }

        mhdf_closeData( filePtr, handle, &status );
        if( MB_SUCCESS != rval ) break;
        if( is_error( status ) )
        {
            rval = MB_FAILURE;
            break;
        }
    }

    if( read_type ) H5Tclose( read_type );
    return rval;
}

ErrorCode ReadHDF5::create_tag( const mhdf_TagDesc& info, Tag& handle, hid_t& hdf_type )
{
    CHECK_OPEN_HANDLES;

    ErrorCode rval;
    mhdf_Status status;
    TagType storage;
    DataType mb_type;
    bool re_read_default = false;

    switch( info.storage )
    {
        case mhdf_DENSE_TYPE:
            storage = MB_TAG_DENSE;
            break;
        case mhdf_SPARSE_TYPE:
            storage = MB_TAG_SPARSE;
            break;
        case mhdf_BIT_TYPE:
            storage = MB_TAG_BIT;
            break;
        case mhdf_MESH_TYPE:
            storage = MB_TAG_MESH;
            break;
        default:
            MB_SET_ERR( MB_FAILURE, "Invalid storage type for tag '" << info.name << "': " << info.storage );
    }

    // Type-specific stuff
    if( info.type == mhdf_BITFIELD )
    {
        if( info.size < 1 || info.size > 8 )
        {
            MB_SET_ERR( MB_FAILURE, "Invalid bit tag: class is MB_TAG_BIT, num bits = " << info.size );
        }
        hdf_type = H5Tcopy( H5T_NATIVE_B8 );
        mb_type  = MB_TYPE_BIT;
        if( hdf_type < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }
    else if( info.type == mhdf_OPAQUE )
    {
        mb_type = MB_TYPE_OPAQUE;

        // Check for user-provided type
        Tag type_handle;
        std::string tag_type_name = "__hdf5_tag_type_";
        tag_type_name += info.name;
        rval = iFace->tag_get_handle( tag_type_name.c_str(), sizeof( hid_t ), MB_TYPE_OPAQUE, type_handle );
        if( MB_SUCCESS == rval )
        {
            EntityHandle root = 0;
            rval              = iFace->tag_get_data( type_handle, &root, 1, &hdf_type );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
            hdf_type        = H5Tcopy( hdf_type );
            re_read_default = true;
        }
        else if( MB_TAG_NOT_FOUND == rval )
        {
            hdf_type = 0;
        }
        else
            MB_SET_ERR( rval, "ReadHDF5 Failure" );

        if( hdf_type < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }
    else
    {
        switch( info.type )
        {
            case mhdf_INTEGER:
                hdf_type = H5T_NATIVE_INT;
                mb_type  = MB_TYPE_INTEGER;
                break;
            case mhdf_FLOAT:
                hdf_type = H5T_NATIVE_DOUBLE;
                mb_type  = MB_TYPE_DOUBLE;
                break;
            case mhdf_BOOLEAN:
                hdf_type = H5T_NATIVE_UINT;
                mb_type  = MB_TYPE_INTEGER;
                break;
            case mhdf_ENTITY_ID:
                hdf_type = handleType;
                mb_type  = MB_TYPE_HANDLE;
                break;
            default:
                MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        if( info.size > 1 )
        {  // Array
            hsize_t tmpsize = info.size;
#if defined( H5Tarray_create_vers ) && H5Tarray_create_vers > 1
            hdf_type = H5Tarray_create2( hdf_type, 1, &tmpsize );
#else
            hdf_type = H5Tarray_create( hdf_type, 1, &tmpsize, NULL );
#endif
        }
        else
        {
            hdf_type = H5Tcopy( hdf_type );
        }
        if( hdf_type < 0 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    // If default or global/mesh value in file, read it.
    if( info.default_value || info.global_value )
    {
        if( re_read_default )
        {
            mhdf_getTagValues( filePtr, info.name, hdf_type, info.default_value, info.global_value, &status );
            if( mhdf_isError( &status ) )
            {
                if( hdf_type ) H5Tclose( hdf_type );
                MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) );
            }
        }

        if( MB_TYPE_HANDLE == mb_type )
        {
            if( info.default_value )
            {
                rval = convert_id_to_handle( (EntityHandle*)info.default_value, info.default_value_size );
                if( MB_SUCCESS != rval )
                {
                    if( hdf_type ) H5Tclose( hdf_type );
                    MB_SET_ERR( rval, "ReadHDF5 Failure" );
                }
            }
            if( info.global_value )
            {
                rval = convert_id_to_handle( (EntityHandle*)info.global_value, info.global_value_size );
                if( MB_SUCCESS != rval )
                {
                    if( hdf_type ) H5Tclose( hdf_type );
                    MB_SET_ERR( rval, "ReadHDF5 Failure" );
                }
            }
        }
    }

    // Get tag handle, creating if necessary
    if( info.size < 0 )
        rval = iFace->tag_get_handle( info.name, info.default_value_size, mb_type, handle,
                                      storage | MB_TAG_CREAT | MB_TAG_VARLEN | MB_TAG_DFTOK, info.default_value );
    else
        rval = iFace->tag_get_handle( info.name, info.size, mb_type, handle, storage | MB_TAG_CREAT | MB_TAG_DFTOK,
                                      info.default_value );
    if( MB_SUCCESS != rval )
    {
        if( hdf_type ) H5Tclose( hdf_type );
        MB_SET_ERR( MB_FAILURE, "Tag type in file does not match type in database for \"" << info.name << "\"" );
    }

    if( info.global_value )
    {
        EntityHandle root = 0;
        if( info.size > 0 )
        {  // Fixed-length tag
            rval = iFace->tag_set_data( handle, &root, 1, info.global_value );
        }
        else
        {
            int tag_size = info.global_value_size;
            rval         = iFace->tag_set_by_ptr( handle, &root, 1, &info.global_value, &tag_size );
        }
        if( MB_SUCCESS != rval )
        {
            if( hdf_type ) H5Tclose( hdf_type );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_dense_tag( Tag tag_handle, const char* ent_name, hid_t hdf_read_type, hid_t data,
                                    long start_id, long num_values )
{
    CHECK_OPEN_HANDLES;

    ErrorCode rval;
    DataType mb_type;

    rval = iFace->tag_get_data_type( tag_handle, mb_type );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    int read_size;
    rval = iFace->tag_get_bytes( tag_handle, read_size );
    if( MB_SUCCESS != rval )  // Wrong function for variable-length tags
        MB_SET_ERR( rval, "ReadHDF5 Failure" );
    // if (MB_TYPE_BIT == mb_type)
    // read_size = (read_size + 7) / 8; // Convert bits to bytes, plus 7 for ceiling

    if( hdf_read_type )
    {  // If not opaque
        hsize_t hdf_size = H5Tget_size( hdf_read_type );
        if( hdf_size != (hsize_t)read_size ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    // Get actual entities read from file
    Range file_ids, handles;
    Range::iterator f_ins = file_ids.begin(), h_ins = handles.begin();
    IDMap::iterator l, u;
    l = idMap.lower_bound( start_id );
    u = idMap.lower_bound( start_id + num_values - 1 );
    if( l != idMap.end() && start_id + num_values > l->begin )
    {
        if( l == u )
        {
            size_t beg = std::max( start_id, l->begin );
            size_t end = std::min( start_id + num_values, u->begin + u->count ) - 1;
            f_ins      = file_ids.insert( f_ins, beg, end );
            h_ins      = handles.insert( h_ins, l->value + ( beg - l->begin ), l->value + ( end - l->begin ) );
        }
        else
        {
            size_t beg = std::max( start_id, l->begin );
            f_ins      = file_ids.insert( f_ins, beg, l->begin + l->count - 1 );
            h_ins      = handles.insert( h_ins, l->value + ( beg - l->begin ), l->value + l->count - 1 );
            for( ++l; l != u; ++l )
            {
                f_ins = file_ids.insert( f_ins, l->begin, l->begin + l->count - 1 );
                h_ins = handles.insert( h_ins, l->value, l->value + l->count - 1 );
            }
            if( u != idMap.end() && u->begin < start_id + num_values )
            {
                size_t end = std::min( start_id + num_values, u->begin + u->count - 1 );
                f_ins      = file_ids.insert( f_ins, u->begin, end );
                h_ins      = handles.insert( h_ins, u->value, u->value + end - u->begin );
            }
        }
    }

    // Given that all of the entities for this dense tag data should
    // have been created as a single contiguous block, the resulting
    // MOAB handle range should be contiguous.
    // THE ABOVE IS NOT NECESSARILY TRUE. SOMETIMES LOWER-DIMENSION
    // ENTS ARE READ AND THEN DELETED FOR PARTIAL READS.
    // assert(handles.empty() || handles.size() == (handles.back() - handles.front() + 1));

    std::string tn( "<error>" );
    iFace->tag_get_name( tag_handle, tn );
    tn += " data for ";
    tn += ent_name;
    try
    {
        h_ins = handles.begin();
        ReadHDF5Dataset reader( tn.c_str(), data, nativeParallel, mpiComm, false );
        long buffer_size = bufferSize / read_size;
        reader.set_file_ids( file_ids, start_id, buffer_size, hdf_read_type );
        dbgOut.printf( 3, "Reading dense data for tag \"%s\" and group \"%s\" in %lu chunks\n", tn.c_str(), ent_name,
                       reader.get_read_count() );
        int nn = 0;
        while( !reader.done() )
        {
            dbgOut.printf( 3, "Reading chunk %d of \"%s\" data\n", ++nn, tn.c_str() );

            size_t count;
            reader.read( dataBuffer, count );

            if( MB_TYPE_HANDLE == mb_type )
            {
                rval = convert_id_to_handle( (EntityHandle*)dataBuffer, count * read_size / sizeof( EntityHandle ) );
                if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
            }

            Range ents;
            Range::iterator end = h_ins;
            end += count;
            ents.insert( h_ins, end );
            h_ins = end;

            rval = iFace->tag_set_data( tag_handle, ents, dataBuffer );
            if( MB_SUCCESS != rval )
            {
                dbgOut.printf( 1, "Internal error setting data for tag \"%s\"\n", tn.c_str() );
                MB_SET_ERR( rval, "ReadHDF5 Failure" );
            }
        }
    }
    catch( ReadHDF5Dataset::Exception )
    {
        dbgOut.printf( 1, "Internal error reading dense data for tag \"%s\"\n", tn.c_str() );
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

// Read entire ID table and for those file IDs corresponding
// to entities that we have read from the file add both the
// offset into the offset range and the handle into the handle
// range. If handles are not ordered, switch to using a vector.
ErrorCode ReadHDF5::read_sparse_tag_indices( const char* name, hid_t id_table,
                                             EntityHandle start_offset,  // Can't put zero in a Range
                                             Range& offset_range, Range& handle_range,
                                             std::vector< EntityHandle >& handle_vect )
{
    CHECK_OPEN_HANDLES;

    offset_range.clear();
    handle_range.clear();
    handle_vect.clear();

    ErrorCode rval;
    Range::iterator handle_hint = handle_range.begin();
    Range::iterator offset_hint = offset_range.begin();

    EntityHandle* idbuf = (EntityHandle*)dataBuffer;
    size_t idbuf_size   = bufferSize / sizeof( EntityHandle );

    std::string tn( name );
    tn += " indices";

    assert( start_offset > 0 );  // Can't put zero in a Range
    try
    {
        ReadHDF5Dataset id_reader( tn.c_str(), id_table, nativeParallel, mpiComm, false );
        id_reader.set_all_file_ids( idbuf_size, handleType );
        size_t offset = start_offset;
        dbgOut.printf( 3, "Reading file ids for sparse tag \"%s\" in %lu chunks\n", name, id_reader.get_read_count() );
        int nn = 0;
        while( !id_reader.done() )
        {
            dbgOut.printf( 3, "Reading chunk %d of \"%s\" IDs\n", ++nn, name );
            size_t count;
            id_reader.read( idbuf, count );

            rval = convert_id_to_handle( idbuf, count );
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

            // idbuf will now contain zero-valued handles for those
            // tag values that correspond to entities we are not reading
            // from the file.
            for( size_t i = 0; i < count; ++i )
            {
                if( idbuf[i] )
                {
                    offset_hint = offset_range.insert( offset_hint, offset + i );
                    if( !handle_vect.empty() ) { handle_vect.push_back( idbuf[i] ); }
                    else if( handle_range.empty() || idbuf[i] > handle_range.back() )
                    {
                        handle_hint = handle_range.insert( handle_hint, idbuf[i] );
                    }
                    else
                    {
                        handle_vect.resize( handle_range.size() );
                        std::copy( handle_range.begin(), handle_range.end(), handle_vect.begin() );
                        handle_range.clear();
                        handle_vect.push_back( idbuf[i] );
                        dbgOut.print( 2, "Switching to unordered list for tag handle list\n" );
                    }
                }
            }

            offset += count;
        }
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_sparse_tag( Tag tag_handle, hid_t hdf_read_type, hid_t id_table, hid_t value_table,
                                     long /*num_values*/ )
{
    CHECK_OPEN_HANDLES;

    // Read entire ID table and for those file IDs corresponding
    // to entities that we have read from the file add both the
    // offset into the offset range and the handle into the handle
    // range.  If handles are not ordered, switch to using a vector.
    const EntityHandle base_offset = 1;  // Can't put zero in a Range
    std::vector< EntityHandle > handle_vect;
    Range handle_range, offset_range;
    std::string tn( "<error>" );
    iFace->tag_get_name( tag_handle, tn );
    ErrorCode rval =
        read_sparse_tag_indices( tn.c_str(), id_table, base_offset, offset_range, handle_range, handle_vect );
    if( MB_SUCCESS != rval ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    DataType mbtype;
    rval = iFace->tag_get_data_type( tag_handle, mbtype );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    int read_size;
    rval = iFace->tag_get_bytes( tag_handle, read_size );
    if( MB_SUCCESS != rval )  // Wrong function for variable-length tags
        MB_SET_ERR( rval, "ReadHDF5 Failure" );
    // if (MB_TYPE_BIT == mbtype)
    // read_size = (read_size + 7) / 8; // Convert bits to bytes, plus 7 for ceiling

    if( hdf_read_type )
    {  // If not opaque
        hsize_t hdf_size = H5Tget_size( hdf_read_type );
        if( hdf_size != (hsize_t)read_size ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    const int handles_per_tag = read_size / sizeof( EntityHandle );

    // Now read data values
    size_t chunk_size = bufferSize / read_size;
    try
    {
        ReadHDF5Dataset val_reader( ( tn + " values" ).c_str(), value_table, nativeParallel, mpiComm, false );
        val_reader.set_file_ids( offset_range, base_offset, chunk_size, hdf_read_type );
        dbgOut.printf( 3, "Reading sparse values for tag \"%s\" in %lu chunks\n", tn.c_str(),
                       val_reader.get_read_count() );
        int nn        = 0;
        size_t offset = 0;
        while( !val_reader.done() )
        {
            dbgOut.printf( 3, "Reading chunk %d of \"%s\" values\n", ++nn, tn.c_str() );
            size_t count;
            val_reader.read( dataBuffer, count );
            if( MB_TYPE_HANDLE == mbtype )
            {
                rval = convert_id_to_handle( (EntityHandle*)dataBuffer, count * handles_per_tag );
                if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
            }

            if( !handle_vect.empty() )
            {
                rval = iFace->tag_set_data( tag_handle, &handle_vect[offset], count, dataBuffer );
                offset += count;
            }
            else
            {
                Range r;
                r.merge( handle_range.begin(), handle_range.begin() + count );
                handle_range.erase( handle_range.begin(), handle_range.begin() + count );
                rval = iFace->tag_set_data( tag_handle, r, dataBuffer );
            }
            if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_var_len_tag( Tag tag_handle, hid_t hdf_read_type, hid_t ent_table, hid_t val_table,
                                      hid_t off_table, long /*num_entities*/, long /*num_values*/ )
{
    CHECK_OPEN_HANDLES;

    ErrorCode rval;
    DataType mbtype;

    rval = iFace->tag_get_data_type( tag_handle, mbtype );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    // Can't do variable-length bit tags
    if( MB_TYPE_BIT == mbtype ) MB_CHK_ERR( MB_VARIABLE_DATA_LENGTH );

    // If here, MOAB tag must be variable-length
    int mbsize;
    if( MB_VARIABLE_DATA_LENGTH != iFace->tag_get_bytes( tag_handle, mbsize ) )
    {
        assert( false );MB_CHK_ERR( MB_VARIABLE_DATA_LENGTH );
    }

    int read_size;
    if( hdf_read_type )
    {
        hsize_t hdf_size = H5Tget_size( hdf_read_type );
        if( hdf_size < 1 ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        read_size = hdf_size;
    }
    else
    {
        // Opaque
        read_size = 1;
    }

    std::string tn( "<error>" );
    iFace->tag_get_name( tag_handle, tn );

    // Read entire ID table and for those file IDs corresponding
    // to entities that we have read from the file add both the
    // offset into the offset range and the handle into the handle
    // range. If handles are not ordered, switch to using a vector.
    const EntityHandle base_offset = 1;  // Can't put zero in a Range
    std::vector< EntityHandle > handle_vect;
    Range handle_range, offset_range;
    rval = read_sparse_tag_indices( tn.c_str(), ent_table, base_offset, offset_range, handle_range, handle_vect );

    // This code only works if the id_table is an ordered list.
    // This assumption was also true for the previous iteration
    // of this code, but wasn't checked. MOAB's file writer
    // always writes an ordered list for id_table.
    if( !handle_vect.empty() ) { MB_SET_ERR( MB_FAILURE, "Unordered file ids for variable length tag not supported" ); }

    class VTReader : public ReadHDF5VarLen
    {
        Tag tagHandle;
        bool isHandle;
        size_t readSize;
        ReadHDF5* readHDF5;

      public:
        ErrorCode store_data( EntityHandle file_id, void* data, long count, bool )
        {
            ErrorCode rval1;
            if( isHandle )
            {
                assert( readSize == sizeof( EntityHandle ) );
                rval1 = readHDF5->convert_id_to_handle( (EntityHandle*)data, count );MB_CHK_ERR( rval1 );
            }
            int n = count;
            return readHDF5->moab()->tag_set_by_ptr( tagHandle, &file_id, 1, &data, &n );
        }
        VTReader( DebugOutput& debug_output, void* buffer, size_t buffer_size, Tag tag, bool is_handle_tag,
                  size_t read_size1, ReadHDF5* owner )
            : ReadHDF5VarLen( debug_output, buffer, buffer_size ), tagHandle( tag ), isHandle( is_handle_tag ),
              readSize( read_size1 ), readHDF5( owner )
        {
        }
    };

    VTReader tool( dbgOut, dataBuffer, bufferSize, tag_handle, MB_TYPE_HANDLE == mbtype, read_size, this );
    try
    {
        // Read offsets into value table.
        std::vector< unsigned > counts;
        Range offsets;
        ReadHDF5Dataset off_reader( ( tn + " offsets" ).c_str(), off_table, nativeParallel, mpiComm, false );
        rval = tool.read_offsets( off_reader, offset_range, base_offset, base_offset, offsets, counts );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

        // Read tag values
        Range empty;
        ReadHDF5Dataset val_reader( ( tn + " values" ).c_str(), val_table, nativeParallel, mpiComm, false );
        rval = tool.read_data( val_reader, offsets, base_offset, hdf_read_type, handle_range, counts, empty );
        if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }
    catch( ReadHDF5Dataset::Exception )
    {
        MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::convert_id_to_handle( EntityHandle* array, size_t size )
{
    convert_id_to_handle( array, size, idMap );
    return MB_SUCCESS;
}

void ReadHDF5::convert_id_to_handle( EntityHandle* array, size_t size, const RangeMap< long, EntityHandle >& id_map )
{
    for( EntityHandle* const end = array + size; array != end; ++array )
        *array = id_map.find( *array );
}

void ReadHDF5::convert_id_to_handle( EntityHandle* array, size_t size, size_t& new_size,
                                     const RangeMap< long, EntityHandle >& id_map )
{
    RangeMap< long, EntityHandle >::const_iterator it;
    new_size = 0;
    for( size_t i = 0; i < size; ++i )
    {
        it = id_map.lower_bound( array[i] );
        if( it != id_map.end() && it->begin <= (long)array[i] )
            array[new_size++] = it->value + ( array[i] - it->begin );
    }
}

void ReadHDF5::convert_range_to_handle( const EntityHandle* ranges, size_t num_ranges,
                                        const RangeMap< long, EntityHandle >& id_map, Range& merge )
{
    RangeMap< long, EntityHandle >::iterator it = id_map.begin();
    Range::iterator hint                        = merge.begin();
    for( size_t i = 0; i < num_ranges; ++i )
    {
        long id        = ranges[2 * i];
        const long end = id + ranges[2 * i + 1];
        // We assume that 'ranges' is sorted, but check just in case it isn't.
        if( it == id_map.end() || it->begin > id ) it = id_map.begin();
        it = id_map.lower_bound( it, id_map.end(), id );
        if( it == id_map.end() ) continue;
        if( id < it->begin ) id = it->begin;
        while( id < end )
        {
            if( id < it->begin ) id = it->begin;
            const long off = id - it->begin;
            long count     = std::min( it->count - off, end - id );
            // It is possible that this new subrange is starting after the end
            // It will result in negative count, which does not make sense
            // We are done with this range, go to the next one
            if( count <= 0 ) break;
            hint = merge.insert( hint, it->value + off, it->value + off + count - 1 );
            id += count;
            if( id < end )
            {
                if( ++it == id_map.end() ) break;
                if( it->begin > end ) break;
            }
        }
    }
}

ErrorCode ReadHDF5::convert_range_to_handle( const EntityHandle* array, size_t num_ranges, Range& range )
{
    convert_range_to_handle( array, num_ranges, idMap, range );
    return MB_SUCCESS;
}

ErrorCode ReadHDF5::insert_in_id_map( const Range& file_ids, EntityHandle start_id )
{
    IDMap tmp_map;
    bool merge = !idMap.empty() && !file_ids.empty() && idMap.back().begin > (long)file_ids.front();
    IDMap& map = merge ? tmp_map : idMap;
    Range::const_pair_iterator p;
    for( p = file_ids.const_pair_begin(); p != file_ids.const_pair_end(); ++p )
    {
        size_t count = p->second - p->first + 1;
        if( !map.insert( p->first, start_id, count ).second ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        start_id += count;
    }
    if( merge && !idMap.merge( tmp_map ) ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::insert_in_id_map( long file_id, EntityHandle handle )
{
    if( !idMap.insert( file_id, handle, 1 ).second ) MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_qa( EntityHandle )
{
    CHECK_OPEN_HANDLES;

    mhdf_Status status;
    // std::vector<std::string> qa_list;

    int qa_len;
    char** qa = mhdf_readHistory( filePtr, &qa_len, &status );
    if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }
    // qa_list.resize(qa_len);
    for( int i = 0; i < qa_len; i++ )
    {
        // qa_list[i] = qa[i];
        free( qa[i] );
    }
    free( qa );

    /** FIX ME - how to put QA list on set?? */

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::store_file_ids( Tag tag )
{
    CHECK_OPEN_HANDLES;

    // typedef int tag_type;
    typedef long tag_type;
    // change it to be able to read much bigger files (long is 64 bits ...)

    tag_type* buffer       = reinterpret_cast< tag_type* >( dataBuffer );
    const long buffer_size = bufferSize / sizeof( tag_type );
    for( IDMap::iterator i = idMap.begin(); i != idMap.end(); ++i )
    {
        IDMap::Range range = *i;

        // Make sure the values will fit in the tag type
        IDMap::key_type rv = range.begin + ( range.count - 1 );
        tag_type tv        = (tag_type)rv;
        if( (IDMap::key_type)tv != rv )
        {
            assert( false );
            return MB_INDEX_OUT_OF_RANGE;
        }

        while( range.count )
        {
            long count = buffer_size < range.count ? buffer_size : range.count;

            Range handles;
            handles.insert( range.value, range.value + count - 1 );
            range.value += count;
            range.count -= count;
            for( long j = 0; j < count; ++j )
                buffer[j] = (tag_type)range.begin++;

            ErrorCode rval = iFace->tag_set_data( tag, handles, buffer );
            if( MB_SUCCESS != rval ) return rval;
        }
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::store_sets_file_ids()
{
    CHECK_OPEN_HANDLES;

    // create a tag that will not be saved, but it will be
    // used by visit plugin to match the sets and their file ids
    // it is the same type as the tag defined in ReadParallelcpp, for file id
    Tag setFileIdTag;
    long default_val = 0;
    ErrorCode rval   = iFace->tag_get_handle( "__FILE_ID_FOR_SETS", sizeof( long ), MB_TYPE_OPAQUE, setFileIdTag,
                                            ( MB_TAG_DENSE | MB_TAG_CREAT ), &default_val );

    if( MB_SUCCESS != rval || 0 == setFileIdTag ) return rval;
    // typedef int tag_type;
    typedef long tag_type;
    // change it to be able to read much bigger files (long is 64 bits ...)

    tag_type* buffer       = reinterpret_cast< tag_type* >( dataBuffer );
    const long buffer_size = bufferSize / sizeof( tag_type );
    for( IDMap::iterator i = idMap.begin(); i != idMap.end(); ++i )
    {
        IDMap::Range range = *i;
        EntityType htype   = iFace->type_from_handle( range.value );
        if( MBENTITYSET != htype ) continue;
        // work only with entity sets
        // Make sure the values will fit in the tag type
        IDMap::key_type rv = range.begin + ( range.count - 1 );
        tag_type tv        = (tag_type)rv;
        if( (IDMap::key_type)tv != rv )
        {
            assert( false );
            return MB_INDEX_OUT_OF_RANGE;
        }

        while( range.count )
        {
            long count = buffer_size < range.count ? buffer_size : range.count;

            Range handles;
            handles.insert( range.value, range.value + count - 1 );
            range.value += count;
            range.count -= count;
            for( long j = 0; j < count; ++j )
                buffer[j] = (tag_type)range.begin++;

            rval = iFace->tag_set_data( setFileIdTag, handles, buffer );
            if( MB_SUCCESS != rval ) return rval;
        }
    }
    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_tag_values( const char* file_name, const char* tag_name, const FileOptions& opts,
                                     std::vector< int >& tag_values_out, const SubsetList* subset_list )
{
    ErrorCode rval;

    rval = set_up_read( file_name, opts );
    if( MB_SUCCESS != rval ) MB_SET_ERR( rval, "ReadHDF5 Failure" );

    int tag_index;
    rval = find_int_tag( tag_name, tag_index );
    if( MB_SUCCESS != rval )
    {
        clean_up_read( opts );
        MB_SET_ERR( rval, "ReadHDF5 Failure" );
    }

    if( subset_list )
    {
        Range file_ids;
        rval = get_subset_ids( subset_list->tag_list, subset_list->tag_list_length, file_ids );
        if( MB_SUCCESS != rval )
        {
            clean_up_read( opts );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }

        rval = read_tag_values_partial( tag_index, file_ids, tag_values_out );
        if( MB_SUCCESS != rval )
        {
            clean_up_read( opts );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }
    else
    {
        rval = read_tag_values_all( tag_index, tag_values_out );
        if( MB_SUCCESS != rval )
        {
            clean_up_read( opts );
            MB_SET_ERR( rval, "ReadHDF5 Failure" );
        }
    }

    return clean_up_read( opts );
}

ErrorCode ReadHDF5::read_tag_values_partial( int tag_index, const Range& file_ids, std::vector< int >& tag_values )
{
    CHECK_OPEN_HANDLES;

    mhdf_Status status;
    const mhdf_TagDesc& tag = fileInfo->tags[tag_index];
    long num_ent, num_val;
    size_t count;
    std::string tn( tag.name );

    // Read sparse values
    if( tag.have_sparse )
    {
        hid_t handles[3];
        mhdf_openSparseTagData( filePtr, tag.name, &num_ent, &num_val, handles, &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }

        try
        {
            // Read all entity handles and fill 'offsets' with ranges of
            // offsets into the data table for entities that we want.
            Range offsets;
            long* buffer           = reinterpret_cast< long* >( dataBuffer );
            const long buffer_size = bufferSize / sizeof( long );
            ReadHDF5Dataset ids( ( tn + " ids" ).c_str(), handles[0], nativeParallel, mpiComm );
            ids.set_all_file_ids( buffer_size, H5T_NATIVE_LONG );
            size_t offset = 0;
            dbgOut.printf( 3, "Reading sparse IDs for tag \"%s\" in %lu chunks\n", tag.name, ids.get_read_count() );
            int nn = 0;
            while( !ids.done() )
            {
                dbgOut.printf( 3, "Reading chunk %d of IDs for \"%s\"\n", ++nn, tag.name );
                ids.read( buffer, count );

                std::sort( buffer, buffer + count );
                Range::iterator ins     = offsets.begin();
                Range::const_iterator i = file_ids.begin();
                for( size_t j = 0; j < count; ++j )
                {
                    while( i != file_ids.end() && (long)*i < buffer[j] )
                        ++i;
                    if( i == file_ids.end() ) break;
                    if( (long)*i == buffer[j] ) { ins = offsets.insert( ins, j + offset, j + offset ); }
                }

                offset += count;
            }

            tag_values.clear();
            tag_values.reserve( offsets.size() );
            const size_t data_buffer_size = bufferSize / sizeof( int );
            int* data_buffer              = reinterpret_cast< int* >( dataBuffer );
            ReadHDF5Dataset vals( ( tn + " sparse vals" ).c_str(), handles[1], nativeParallel, mpiComm );
            vals.set_file_ids( offsets, 0, data_buffer_size, H5T_NATIVE_INT );
            dbgOut.printf( 3, "Reading sparse values for tag \"%s\" in %lu chunks\n", tag.name, vals.get_read_count() );
            nn = 0;
            // Should normally only have one read call, unless sparse nature
            // of file_ids caused reader to do something strange
            while( !vals.done() )
            {
                dbgOut.printf( 3, "Reading chunk %d of values for \"%s\"\n", ++nn, tag.name );
                vals.read( data_buffer, count );
                tag_values.insert( tag_values.end(), data_buffer, data_buffer + count );
            }
        }
        catch( ReadHDF5Dataset::Exception )
        {
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
    }

    std::sort( tag_values.begin(), tag_values.end() );
    tag_values.erase( std::unique( tag_values.begin(), tag_values.end() ), tag_values.end() );

    // Read dense values
    std::vector< int > prev_data, curr_data;
    for( int i = 0; i < tag.num_dense_indices; ++i )
    {
        int grp            = tag.dense_elem_indices[i];
        const char* gname  = 0;
        mhdf_EntDesc* desc = 0;
        if( grp == -1 )
        {
            gname = mhdf_node_type_handle();
            desc  = &fileInfo->nodes;
        }
        else if( grp == -2 )
        {
            gname = mhdf_set_type_handle();
            desc  = &fileInfo->sets;
        }
        else
        {
            assert( grp >= 0 && grp < fileInfo->num_elem_desc );
            gname = fileInfo->elems[grp].handle;
            desc  = &fileInfo->elems[grp].desc;
        }

        Range::iterator s = file_ids.lower_bound( ( EntityHandle )( desc->start_id ) );
        Range::iterator e = Range::lower_bound( s, file_ids.end(), ( EntityHandle )( desc->start_id ) + desc->count );
        Range subset;
        subset.merge( s, e );

        hid_t handle = mhdf_openDenseTagData( filePtr, tag.name, gname, &num_val, &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }

        try
        {
            curr_data.clear();
            tag_values.reserve( subset.size() );
            const size_t data_buffer_size = bufferSize / sizeof( int );
            int* data_buffer              = reinterpret_cast< int* >( dataBuffer );

            ReadHDF5Dataset reader( ( tn + " dense vals" ).c_str(), handle, nativeParallel, mpiComm );
            reader.set_file_ids( subset, desc->start_id, data_buffer_size, H5T_NATIVE_INT );
            dbgOut.printf( 3, "Reading dense data for tag \"%s\" and group \"%s\" in %lu chunks\n", tag.name,
                           fileInfo->elems[grp].handle, reader.get_read_count() );
            int nn = 0;
            // Should normally only have one read call, unless sparse nature
            // of file_ids caused reader to do something strange
            while( !reader.done() )
            {
                dbgOut.printf( 3, "Reading chunk %d of \"%s\"/\"%s\"\n", ++nn, tag.name, fileInfo->elems[grp].handle );
                reader.read( data_buffer, count );
                curr_data.insert( curr_data.end(), data_buffer, data_buffer + count );
            }
        }
        catch( ReadHDF5Dataset::Exception )
        {
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        std::sort( curr_data.begin(), curr_data.end() );
        curr_data.erase( std::unique( curr_data.begin(), curr_data.end() ), curr_data.end() );
        prev_data.clear();
        tag_values.swap( prev_data );
        std::set_union( prev_data.begin(), prev_data.end(), curr_data.begin(), curr_data.end(),
                        std::back_inserter( tag_values ) );
    }

    return MB_SUCCESS;
}

ErrorCode ReadHDF5::read_tag_values_all( int tag_index, std::vector< int >& tag_values )
{
    CHECK_OPEN_HANDLES;

    mhdf_Status status;
    const mhdf_TagDesc& tag = fileInfo->tags[tag_index];
    long junk, num_val;

    // Read sparse values
    if( tag.have_sparse )
    {
        hid_t handles[3];
        mhdf_openSparseTagData( filePtr, tag.name, &junk, &num_val, handles, &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }

        mhdf_closeData( filePtr, handles[0], &status );
        if( mhdf_isError( &status ) )
        {
            MB_SET_ERR_CONT( mhdf_message( &status ) );
            mhdf_closeData( filePtr, handles[1], &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        hid_t file_type = H5Dget_type( handles[1] );
        tag_values.resize( num_val );
        mhdf_readTagValuesWithOpt( handles[1], 0, num_val, file_type, &tag_values[0], collIO, &status );
        if( mhdf_isError( &status ) )
        {
            MB_SET_ERR_CONT( mhdf_message( &status ) );
            H5Tclose( file_type );
            mhdf_closeData( filePtr, handles[1], &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }
        H5Tconvert( file_type, H5T_NATIVE_INT, num_val, &tag_values[0], 0, H5P_DEFAULT );
        H5Tclose( file_type );

        mhdf_closeData( filePtr, handles[1], &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }
    }

    std::sort( tag_values.begin(), tag_values.end() );
    tag_values.erase( std::unique( tag_values.begin(), tag_values.end() ), tag_values.end() );

    // Read dense values
    std::vector< int > prev_data, curr_data;
    for( int i = 0; i < tag.num_dense_indices; ++i )
    {
        int grp           = tag.dense_elem_indices[i];
        const char* gname = 0;
        if( grp == -1 )
            gname = mhdf_node_type_handle();
        else if( grp == -2 )
            gname = mhdf_set_type_handle();
        else
            gname = fileInfo->elems[grp].handle;
        hid_t handle = mhdf_openDenseTagData( filePtr, tag.name, gname, &num_val, &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }

        hid_t file_type = H5Dget_type( handle );
        curr_data.resize( num_val );
        mhdf_readTagValuesWithOpt( handle, 0, num_val, file_type, &curr_data[0], collIO, &status );
        if( mhdf_isError( &status ) )
        {
            MB_SET_ERR_CONT( mhdf_message( &status ) );
            H5Tclose( file_type );
            mhdf_closeData( filePtr, handle, &status );
            MB_SET_ERR( MB_FAILURE, "ReadHDF5 Failure" );
        }

        H5Tconvert( file_type, H5T_NATIVE_INT, num_val, &curr_data[0], 0, H5P_DEFAULT );
        H5Tclose( file_type );
        mhdf_closeData( filePtr, handle, &status );
        if( mhdf_isError( &status ) ) { MB_SET_ERR( MB_FAILURE, mhdf_message( &status ) ); }

        std::sort( curr_data.begin(), curr_data.end() );
        curr_data.erase( std::unique( curr_data.begin(), curr_data.end() ), curr_data.end() );

        prev_data.clear();
        tag_values.swap( prev_data );
        std::set_union( prev_data.begin(), prev_data.end(), curr_data.begin(), curr_data.end(),
                        std::back_inserter( tag_values ) );
    }

    return MB_SUCCESS;
}
void ReadHDF5::print_times()
{
#ifdef MOAB_HAVE_MPI
    if( !myPcomm )
    {
        double recv[NUM_TIMES];
        MPI_Reduce( (void*)_times, recv, NUM_TIMES, MPI_DOUBLE, MPI_MAX, 0, myPcomm->proc_config().proc_comm() );
        for( int i = 0; i < NUM_TIMES; i++ )
            _times[i] = recv[i];  // just get the max from all of them
    }
    if( 0 == myPcomm->proc_config().proc_rank() )
    {
#endif

        std::cout << "ReadHDF5:             " << _times[TOTAL_TIME] << std::endl
                  << "  get set meta        " << _times[SET_META_TIME] << std::endl
                  << "  partial subsets     " << _times[SUBSET_IDS_TIME] << std::endl
                  << "  partition time      " << _times[GET_PARTITION_TIME] << std::endl
                  << "  get set ids         " << _times[GET_SET_IDS_TIME] << std::endl
                  << "  set contents        " << _times[GET_SET_CONTENTS_TIME] << std::endl
                  << "  polyhedra           " << _times[GET_POLYHEDRA_TIME] << std::endl
                  << "  elements            " << _times[GET_ELEMENTS_TIME] << std::endl
                  << "  nodes               " << _times[GET_NODES_TIME] << std::endl
                  << "  node adjacency      " << _times[GET_NODEADJ_TIME] << std::endl
                  << "  side elements       " << _times[GET_SIDEELEM_TIME] << std::endl
                  << "  update connectivity " << _times[UPDATECONN_TIME] << std::endl
                  << "  adjacency           " << _times[ADJACENCY_TIME] << std::endl
                  << "  delete non_adj      " << _times[DELETE_NON_SIDEELEM_TIME] << std::endl
                  << "  recursive sets      " << _times[READ_SET_IDS_RECURS_TIME] << std::endl
                  << "  find contain_sets   " << _times[FIND_SETS_CONTAINING_TIME] << std::endl
                  << "  read sets           " << _times[READ_SETS_TIME] << std::endl
                  << "  read tags           " << _times[READ_TAGS_TIME] << std::endl
                  << "  store file ids      " << _times[STORE_FILE_IDS_TIME] << std::endl
                  << "  read qa records     " << _times[READ_QA_TIME] << std::endl;

#ifdef MOAB_HAVE_MPI
    }
#endif
}

}  // namespace moab
