/*****************************************************************************
*
* Copyright (c) 2003-2018 by The University of Queensland
* http://www.uq.edu.au
*
* Primary Business: Queensland, Australia
* Licensed under the Apache License, version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Development until 2012 by Earth Systems Science Computational Center (ESSCC)
* Development 2012-2013 by School of Earth Sciences
* Development from 2014 by Centre for Geoscience Computing (GeoComp)
*
*****************************************************************************/

#include <finley/DomainFactory.h>

#include <escript/index.h>
#include <escript/SubWorld.h>

#ifdef ESYS_HAVE_NETCDF
 #ifdef NETCDF4
  #include <ncDim.h>
  #include <ncVar.h>
  #include <ncFile.h>

 #include <escript/NCHelper.h>

 #else
  #include <netcdfcpp.h>
 #endif
#endif

#include <boost/python/extract.hpp>
#include <boost/scoped_array.hpp>

#include <sstream>

using namespace std;
using namespace escript;
namespace bp = boost::python;

#ifdef NETCDF4
using namespace netCDF;
#endif

namespace finley {

#ifdef ESYS_HAVE_NETCDF
#ifdef NETCDF4

// A convenience method to retrieve an integer attribute from a NetCDF file
template<typename T>
T ncReadAtt(NcFile& dataFile, const string& fName, const string& attrName)
{
    NcGroupAtt attr = dataFile.getAtt(attrName.c_str());
    if (attr.isNull()) {
        stringstream msg;
        msg << "loadMesh: Error retrieving integer attribute '" << attrName
            << "' from NetCDF file '" << fName << "'";
        throw IOError(msg.str());
    }
    T value;
    attr.getValues(&value);
    return value;
}
#else

// A convenience method to retrieve an integer attribute from a NetCDF file
template<typename T>
T ncReadAtt(NcFile* dataFile, const string& fName, const string& attrName)
{
    NcAtt* attr = dataFile->get_att(attrName.c_str());
    if (!attr) {
        stringstream msg;
        msg << "loadMesh: Error retrieving integer attribute '" << attrName
            << "' from NetCDF file '" << fName << "'";
        throw IOError(msg.str());
    }
    T value = (sizeof(T) > 4 ? attr->as_long(0) : attr->as_int(0));
    delete attr;
    return value;
}

#endif
#endif

inline void cleanupAndThrow(FinleyDomain* dom, string msg)
{
    delete dom;
    string msgPrefix("loadMesh: NetCDF operation failed - ");
    throw IOError(msgPrefix+msg);
}

#ifdef NETCDF4

Domain_ptr FinleyDomain::load(const string& fileName)
{
#ifdef ESYS_HAVE_NETCDF
    JMPI mpiInfo = makeInfo(MPI_COMM_WORLD);
    const string fName(mpiInfo->appendRankToFileName(fileName));

    // Open NetCDF file for reading
    NcGroupAtt attr;
    NcVar nc_var_temp;
    NcFile dataFile;
    // Create the NetCDF file.
    if (!openNcFile(dataFile, fileName))
    {
        throw IOError("load: opening of netCDF file for input failed.");
    }
    // Read NetCDF integer attributes

    // index_size was only introduced with 64-bit index support so fall back
    // to 32 bits if not found.
    int index_size;
    try {
        index_size = ncReadAtt<int>(dataFile, fName, "index_size");
    } catch (IOError& e) {
        index_size = 4;
    }
    // technically we could cast if reading 32-bit data on 64-bit escript
    // but cost-benefit analysis clearly favours this implementation for now
    if (sizeof(index_t) != index_size) {
        throw IOError("loadMesh: size of index types at runtime differ from dump file");
    }

    int mpi_size = ncReadAtt<int>(dataFile, fName, "mpi_size");
    int mpi_rank = ncReadAtt<int>(dataFile, fName, "mpi_rank");
    int numDim = ncReadAtt<int>(dataFile, fName, "numDim");
    int order = ncReadAtt<int>(dataFile, fName, "order");
    int reduced_order = ncReadAtt<int>(dataFile, fName, "reduced_order");
    dim_t numNodes = ncReadAtt<dim_t>(dataFile, fName, "numNodes");
    dim_t num_Elements = ncReadAtt<dim_t>(dataFile, fName, "num_Elements");
    dim_t num_FaceElements = ncReadAtt<dim_t>(dataFile, fName, "num_FaceElements");
    dim_t num_ContactElements = ncReadAtt<dim_t>(dataFile, fName, "num_ContactElements");
    dim_t num_Points = ncReadAtt<dim_t>(dataFile, fName, "num_Points");
    int num_Elements_numNodes = ncReadAtt<int>(dataFile, fName, "num_Elements_numNodes");
    int Elements_TypeId = ncReadAtt<int>(dataFile, fName, "Elements_TypeId");
    int num_FaceElements_numNodes = ncReadAtt<int>(dataFile, fName, "num_FaceElements_numNodes");
    int FaceElements_TypeId = ncReadAtt<int>(dataFile, fName, "FaceElements_TypeId");
    int num_ContactElements_numNodes = ncReadAtt<int>(dataFile, fName, "num_ContactElements_numNodes");
    int ContactElements_TypeId = ncReadAtt<int>(dataFile, fName, "ContactElements_TypeId");
    int Points_TypeId = ncReadAtt<int>(dataFile, fName, "Points_TypeId");
    int num_Tags = ncReadAtt<int>(dataFile, fName, "num_Tags");

    // Verify size and rank
    if (mpiInfo->size != mpi_size) {
        stringstream msg;
        msg << "loadMesh: The NetCDF file '" << fName
            << "' can only be read on " << mpi_size
            << " CPUs. Currently running: " << mpiInfo->size;
        throw FinleyException(msg.str());
    }
    if (mpiInfo->rank != mpi_rank) {
        stringstream msg;
        msg << "loadMesh: The NetCDF file '" << fName
            << "' should be read on CPU #" << mpi_rank
            << " and NOT on #" << mpiInfo->rank;
        throw FinleyException(msg.str());
    }

    // Read mesh name
    if ((attr=dataFile.getAtt("Name")).isNull() ) {
        stringstream msg;
        msg << "loadMesh: Error retrieving mesh name from NetCDF file '"
            << fName << "'";
        throw IOError(msg.str());
    }
    string name;
    attr.getValues(name);

    // allocate mesh
    FinleyDomain* dom = new FinleyDomain(name, numDim, mpiInfo);

    // read nodes
    NodeFile* nodes = dom->getNodes();
    nodes->allocTable(numNodes);

    try
    {
        // Nodes_Id
        nc_var_temp = dataFile.getVar("Nodes_Id");
        nc_var_temp.getVar(&nodes->Id[0]); // numNodes values read
        // Nodes_Tag
        nc_var_temp = dataFile.getVar("Nodes_Tag");
        nc_var_temp.getVar(&nodes->Tag[0]);   // numNodes
        // Nodes_gDOF
        nc_var_temp = dataFile.getVar("Nodes_gDOF");
        nc_var_temp.getVar(&nodes->globalDegreesOfFreedom[0]);   //numNodes
        // Nodes_gNI
        nc_var_temp = dataFile.getVar("Nodes_gNI");
        nc_var_temp.getVar(&nodes->globalNodesIndex[0]); // numNodes
        // Nodes_grDfI
        nc_var_temp = dataFile.getVar("Nodes_grDfI");
        nc_var_temp.getVar(&nodes->globalReducedDOFIndex[0]); // numNodes
        // Nodes_grNI
        nc_var_temp = dataFile.getVar("Nodes_grNI");
        nc_var_temp.getVar(&nodes->globalReducedNodesIndex[0]);  // numNodes
        // Nodes_Coordinates
        nc_var_temp = dataFile.getVar("Nodes_Coordinates");
        nc_var_temp.getVar(&nodes->Coordinates[0]); // (numNodes, numDim)
    }
    catch (exceptions::NcException& e)
    {
        cleanupAndThrow(dom, "Read vars from file");
    }
    nodes->updateTagList();

    // read elements
    const_ReferenceElementSet_ptr refElements(new ReferenceElementSet(
                (ElementTypeId)Elements_TypeId, order, reduced_order));
    ElementFile* elements = new ElementFile(refElements, mpiInfo);
    dom->setElements(elements);
    elements->allocTable(num_Elements);
    elements->minColor = 0;
    elements->maxColor = num_Elements-1;
    if (num_Elements > 0) {
        try
        {
            // Elements_Id
            nc_var_temp = dataFile.getVar("Elements_Id");
            nc_var_temp.getVar(&elements->Id[0]);    // num_Elements
            // Elements_Tag
            nc_var_temp = dataFile.getVar("Elements_Tag");
            nc_var_temp.getVar(&elements->Tag[0]); // num_Elements
            // Elements_Owner
            nc_var_temp = dataFile.getVar("Elements_Owner");
            nc_var_temp.getVar(&elements->Owner[0]);  // num_Elements
            // Elements_Color
            nc_var_temp = dataFile.getVar("Elements_Color");
            nc_var_temp.getVar(&elements->Color[0]); // num_Elements
        }
        catch (exceptions::NcException& e)
        {
            cleanupAndThrow(dom, "Readig element vars");
        }
       // Now we need to adjust maxColor
       index_t mc = elements->Color[0];
       for (index_t i = 1; i < num_Elements; ++i) {
           if (mc < elements->Color[i]) {
               mc = elements->Color[i];
           }
       }
       elements->maxColor = mc;
       // Elements_Nodes
       int* Elements_Nodes = new int[num_Elements*num_Elements_numNodes];
       try
       {
            nc_var_temp = dataFile.getVar("Elements_Nodes");
            nc_var_temp.getVar(&Elements_Nodes[0]);    // (num_Elements, num_Elements_numNodes) )
       }
       catch (exceptions::NcException& e)
       {
           delete[] Elements_Nodes;
           cleanupAndThrow(dom, "get_var(Elements_Nodes)");
       }
       // Copy temp array into elements->Nodes
       for (index_t i = 0; i < num_Elements; i++) {
           for (int j = 0; j < num_Elements_numNodes; j++) {
               elements->Nodes[INDEX2(j,i,num_Elements_numNodes)]
                    = Elements_Nodes[INDEX2(j,i,num_Elements_numNodes)];
           }
       }
       delete[] Elements_Nodes;
    } // num_Elements > 0
    elements->updateTagList();

    // get the face elements
    const_ReferenceElementSet_ptr refFaceElements(
            new ReferenceElementSet((ElementTypeId)FaceElements_TypeId,
                order, reduced_order));
    ElementFile* faces = new ElementFile(refFaceElements, mpiInfo);
    dom->setFaceElements(faces);
    faces->allocTable(num_FaceElements);
    faces->minColor = 0;
    faces->maxColor = num_FaceElements-1;
    if (num_FaceElements > 0) {
        try
        {
            // FaceElements_Id
            nc_var_temp = dataFile.getVar("FaceElements_Id");
            nc_var_temp.getVar(&faces->Id[0]); // num_FaceElements
            // FaceElements_Tag
            nc_var_temp = dataFile.getVar("FaceElements_Tag");
            nc_var_temp.getVar(&faces->Tag[0]); // num_FaceElements
            // FaceElements_Owner
            nc_var_temp = dataFile.getVar("FaceElements_Owner");
            nc_var_temp.getVar(&faces->Owner[0]);   // num_FaceElements
            // FaceElements_Color
            nc_var_temp = dataFile.getVar("FaceElements_Color");
            nc_var_temp.getVar(&faces->Color[0]); // num_FaceElements
        }
        catch (exceptions::NcException& e)
        {
            cleanupAndThrow(dom, "read face variables");
        }
        // Now we need to adjust maxColor
        index_t mc = faces->Color[0];
        for (index_t i = 1; i < num_FaceElements; ++i) {
            if (mc < faces->Color[i]) {
                mc = faces->Color[i];
            }
        }
        faces->maxColor = mc;
        // FaceElements_Nodes
        int* FaceElements_Nodes = new int[num_FaceElements*num_FaceElements_numNodes];
        try
        {
            nc_var_temp = dataFile.getVar("FaceElements_Nodes");
            nc_var_temp.getVar(&(FaceElements_Nodes[0])); // num_FaceElements, num_FaceElements_numNodes) )
        }
        catch (exceptions::NcException& e)
        {
            delete[] FaceElements_Nodes;
            cleanupAndThrow(dom, "read face elements");
        }
        if ((nc_var_temp = dataFile.getVar("FaceElements_Nodes")), nc_var_temp.isNull()) {
            delete[] FaceElements_Nodes;
            cleanupAndThrow(dom, "get_var(FaceElements_Nodes)");
        }
        nc_var_temp.getVar(&(FaceElements_Nodes[0]));  // num_FaceElements, num_FaceElements_numNodes
        // Copy temp array into faces->Nodes
        for (index_t i = 0; i < num_FaceElements; i++) {
            for (int j = 0; j < num_FaceElements_numNodes; j++) {
                faces->Nodes[INDEX2(j,i,num_FaceElements_numNodes)] = FaceElements_Nodes[INDEX2(j,i,num_FaceElements_numNodes)];
            }
        }
        delete[] FaceElements_Nodes;
    } // num_FaceElements > 0
    faces->updateTagList();

    // get the Contact elements
    const_ReferenceElementSet_ptr refContactElements(
         new ReferenceElementSet((ElementTypeId)ContactElements_TypeId,
             order, reduced_order));
    ElementFile* contacts = new ElementFile(refContactElements, mpiInfo);
    dom->setContactElements(contacts);
    contacts->allocTable(num_ContactElements);
    contacts->minColor = 0;
    contacts->maxColor = num_ContactElements-1;
    if (num_ContactElements > 0) {
        // ContactElements_Id
        if (( nc_var_temp = dataFile.getVar("ContactElements_Id")), nc_var_temp.isNull() )
            cleanupAndThrow(dom, "get_var(ContactElements_Id)");
        nc_var_temp.getVar(&contacts->Id[0]);       // num_ContactElements
        // ContactElements_Tag
        if (( nc_var_temp = dataFile.getVar("ContactElements_Tag")), nc_var_temp.isNull() )
            cleanupAndThrow(dom, "get_var(ContactElements_Tag)");
        nc_var_temp.getVar(&contacts->Tag[0]);  //, num_ContactElements
        // ContactElements_Owner
        if (( nc_var_temp = dataFile.getVar("ContactElements_Owner")), nc_var_temp.isNull() )
            cleanupAndThrow(dom, "get_var(ContactElements_Owner)");
        nc_var_temp.getVar(&contacts->Owner[0]);    //, num_ContactElements)
        // ContactElements_Color
        if (( nc_var_temp = dataFile.getVar("ContactElements_Color")), nc_var_temp.isNull() )
            cleanupAndThrow(dom, "get_var(ContactElements_Color)");
        nc_var_temp.getVar(&contacts->Color[0]);  //, num_ContactElements
        // Now we need to adjust maxColor
        index_t mc = contacts->Color[0];
        for (index_t i = 1; i < num_ContactElements; ++i) {
            if (mc < contacts->Color[i]) {
                mc = contacts->Color[i];
            }
        }
        contacts->maxColor = mc;
        // ContactElements_Nodes
        int* ContactElements_Nodes = new int[num_ContactElements*num_ContactElements_numNodes];
        if ((nc_var_temp = dataFile.getVar("ContactElements_Nodes")), nc_var_temp.isNull()) {
            delete[] ContactElements_Nodes;
            cleanupAndThrow(dom, "get_var(ContactElements_Nodes)");
        }
        nc_var_temp.getVar(&ContactElements_Nodes[0]);    // num_ContactElements, num_ContactElements_numNodes
        // Copy temp array into contacts->Nodes
        for (index_t i = 0; i < num_ContactElements; i++) {
            for (int j = 0; j < num_ContactElements_numNodes; j++) {
                contacts->Nodes[INDEX2(j,i,num_ContactElements_numNodes)] = ContactElements_Nodes[INDEX2(j,i,num_ContactElements_numNodes)];
            }
        }
        delete[] ContactElements_Nodes;
    } // num_ContactElements > 0
    contacts->updateTagList();

    // get the Points (nodal elements)
    const_ReferenceElementSet_ptr refPoints(new ReferenceElementSet(
                (ElementTypeId)Points_TypeId, order, reduced_order));
    ElementFile* points = new ElementFile(refPoints, mpiInfo);
    dom->setPoints(points);
    points->allocTable(num_Points);
    points->minColor = 0;
    points->maxColor = num_Points-1;
    if (num_Points > 0) {
        // Points_Id
        if (( nc_var_temp = dataFile.getVar("Points_Id")), nc_var_temp.isNull())
            cleanupAndThrow(dom, "get_var(Points_Id)");
        nc_var_temp.getVar(&points->Id[0]); // num_Points
        // Points_Tag
        if (( nc_var_temp = dataFile.getVar("Points_Tag")), nc_var_temp.isNull())
            cleanupAndThrow(dom, "get_var(Points_Tag)");
        nc_var_temp.getVar(&points->Tag[0]);    // num_Points
        // Points_Owner
        if (( nc_var_temp = dataFile.getVar("Points_Owner")), nc_var_temp.isNull())
            cleanupAndThrow(dom, "get_var(Points_Owner)");
        nc_var_temp.getVar(&points->Owner[0]);  // num_Points
        // Points_Color
        if (( nc_var_temp = dataFile.getVar("Points_Color")), nc_var_temp.isNull())
            cleanupAndThrow(dom, "get_var(Points_Color)");
        nc_var_temp.getVar(&points->Color[0]);  // num_Points
        // Now we need to adjust maxColor
        index_t mc = points->Color[0];
        for (index_t i = 1; i < num_Points; ++i) {
            if (mc < points->Color[i]) {
                mc = points->Color[i];
            }
        }
        points->maxColor = mc;
        // Points_Nodes
        int* Points_Nodes = new int[num_Points];
        if ((nc_var_temp = dataFile.getVar("Points_Nodes")), nc_var_temp.isNull()) {
            delete[] Points_Nodes;
            cleanupAndThrow(dom, "get_var(Points_Nodes)");
        }
        nc_var_temp.getVar(&Points_Nodes[0]);  // num_Points
        // Copy temp array into points->Nodes
        for (index_t i = 0; i < num_Points; i++) {
            points->Id[points->Nodes[INDEX2(0,i,1)]] = Points_Nodes[i];
        }
        delete[] Points_Nodes;
    } // num_Points > 0
    points->updateTagList();

    // get the tags
    if (num_Tags > 0) {
        // Temp storage to gather node IDs
        int *Tags_keys = new int[num_Tags];
        char name_temp[4096];
        int i;

        // Tags_keys
        if (( nc_var_temp = dataFile.getVar("Tags_keys")), nc_var_temp.isNull() ) {
            delete[] Tags_keys;
            cleanupAndThrow(dom, "get_var(Tags_keys)");
        }
        nc_var_temp.getVar(&Tags_keys[0]); // num_Tags
        for (i=0; i<num_Tags; i++) {
          // Retrieve tag name
          sprintf(name_temp, "Tags_name_%d", i);
          if ((attr=dataFile.getAtt(name_temp)), attr.isNull() ) {
              delete[] Tags_keys;
              stringstream msg;
              msg << "get_att(" << name_temp << ")";
              cleanupAndThrow(dom, msg.str());
          }
          std::string name;
          attr.getValues(name);
//           boost::scoped_array<char> name(attr->as_string(0));
//           delete attr;
//           dom->setTagMap(name.get(), Tags_keys[i]);
          dom->setTagMap(name.c_str(), Tags_keys[i]);
        }
        delete[] Tags_keys;
    }

    // Nodes_DofDistribution
    IndexVector first_DofComponent(mpi_size+1);
    if ((nc_var_temp = dataFile.getVar("Nodes_DofDistribution")), nc_var_temp.isNull() ) {
        cleanupAndThrow(dom, "get_var(Nodes_DofDistribution)");
    }
    nc_var_temp.getVar(&first_DofComponent[0]); // mpi_size+1

    // Nodes_NodeDistribution
    IndexVector first_NodeComponent(mpi_size+1);
    if ((nc_var_temp = dataFile.getVar("Nodes_NodeDistribution")), nc_var_temp.isNull() ) {
        cleanupAndThrow(dom, "get_var(Nodes_NodeDistribution)");
    }
    nc_var_temp.getVar(&first_NodeComponent[0]);    // mpi_size+1
    dom->createMappings(first_DofComponent, first_NodeComponent);

    return dom->getPtr();
#else
    throw FinleyException("loadMesh: not compiled with NetCDF. Please contact your installation manager.");
#endif // ESYS_HAVE_NETCDF
}

#else

Domain_ptr FinleyDomain::load(const string& fileName)
{
#ifdef ESYS_HAVE_NETCDF
    JMPI mpiInfo = makeInfo(MPI_COMM_WORLD);
    const string fName(mpiInfo->appendRankToFileName(fileName));

    // Open NetCDF file for reading
    NcAtt *attr;
    NcVar *nc_var_temp;
    // netCDF error handler
    NcError err(NcError::silent_nonfatal);
    // Create the NetCDF file.
    NcFile dataFile(fName.c_str(), NcFile::ReadOnly);
    if (!dataFile.is_valid()) {
        stringstream msg;
        msg << "loadMesh: Opening NetCDF file '" << fName << "' for reading failed.";
        throw IOError(msg.str());
    }

    // Read NetCDF integer attributes

    // index_size was only introduced with 64-bit index support so fall back
    // to 32 bits if not found.
    int index_size;
    try {
        index_size = ncReadAtt<int>(&dataFile, fName, "index_size");
    } catch (IOError& e) {
        index_size = 4;
    }
    // technically we could cast if reading 32-bit data on 64-bit escript
    // but cost-benefit analysis clearly favours this implementation for now
    if (sizeof(index_t) != index_size) {
        throw IOError("loadMesh: size of index types at runtime differ from dump file");
    }

    int mpi_size = ncReadAtt<int>(&dataFile, fName, "mpi_size");
    int mpi_rank = ncReadAtt<int>(&dataFile, fName, "mpi_rank");
    int numDim = ncReadAtt<int>(&dataFile, fName, "numDim");
    int order = ncReadAtt<int>(&dataFile, fName, "order");
    int reduced_order = ncReadAtt<int>(&dataFile, fName, "reduced_order");
    dim_t numNodes = ncReadAtt<dim_t>(&dataFile, fName, "numNodes");
    dim_t num_Elements = ncReadAtt<dim_t>(&dataFile, fName, "num_Elements");
    dim_t num_FaceElements = ncReadAtt<dim_t>(&dataFile, fName, "num_FaceElements");
    dim_t num_ContactElements = ncReadAtt<dim_t>(&dataFile, fName, "num_ContactElements");
    dim_t num_Points = ncReadAtt<dim_t>(&dataFile, fName, "num_Points");
    int num_Elements_numNodes = ncReadAtt<int>(&dataFile, fName, "num_Elements_numNodes");
    int Elements_TypeId = ncReadAtt<int>(&dataFile, fName, "Elements_TypeId");
    int num_FaceElements_numNodes = ncReadAtt<int>(&dataFile, fName, "num_FaceElements_numNodes");
    int FaceElements_TypeId = ncReadAtt<int>(&dataFile, fName, "FaceElements_TypeId");
    int num_ContactElements_numNodes = ncReadAtt<int>(&dataFile, fName, "num_ContactElements_numNodes");
    int ContactElements_TypeId = ncReadAtt<int>(&dataFile, fName, "ContactElements_TypeId");
    int Points_TypeId = ncReadAtt<int>(&dataFile, fName, "Points_TypeId");
    int num_Tags = ncReadAtt<int>(&dataFile, fName, "num_Tags");

    // Verify size and rank
    if (mpiInfo->size != mpi_size) {
        stringstream msg;
        msg << "loadMesh: The NetCDF file '" << fName
            << "' can only be read on " << mpi_size
            << " CPUs. Currently running: " << mpiInfo->size;
        throw FinleyException(msg.str());
    }
    if (mpiInfo->rank != mpi_rank) {
        stringstream msg;
        msg << "loadMesh: The NetCDF file '" << fName
            << "' should be read on CPU #" << mpi_rank
            << " and NOT on #" << mpiInfo->rank;
        throw FinleyException(msg.str());
    }

    // Read mesh name
    if (! (attr=dataFile.get_att("Name")) ) {
        stringstream msg;
        msg << "loadMesh: Error retrieving mesh name from NetCDF file '"
            << fName << "'";
        throw IOError(msg.str());
    }
    boost::scoped_array<char> name(attr->as_string(0));
    delete attr;

    // allocate mesh
    FinleyDomain* dom = new FinleyDomain(name.get(), numDim, mpiInfo);

    // read nodes
    NodeFile* nodes = dom->getNodes();
    nodes->allocTable(numNodes);
    // Nodes_Id
    if (! ( nc_var_temp = dataFile.get_var("Nodes_Id")) )
        cleanupAndThrow(dom, "get_var(Nodes_Id)");
    if (! nc_var_temp->get(&nodes->Id[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_Id)");
    // Nodes_Tag
    if (! ( nc_var_temp = dataFile.get_var("Nodes_Tag")) )
        cleanupAndThrow(dom, "get_var(Nodes_Tag)");
    if (! nc_var_temp->get(&nodes->Tag[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_Tag)");
    // Nodes_gDOF
    if (! ( nc_var_temp = dataFile.get_var("Nodes_gDOF")) )
        cleanupAndThrow(dom, "get_var(Nodes_gDOF)");
    if (! nc_var_temp->get(&nodes->globalDegreesOfFreedom[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_gDOF)");
    // Nodes_gNI
    if (! ( nc_var_temp = dataFile.get_var("Nodes_gNI")) )
        cleanupAndThrow(dom, "get_var(Nodes_gNI)");
    if (! nc_var_temp->get(&nodes->globalNodesIndex[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_gNI)");
    // Nodes_grDfI
    if (! ( nc_var_temp = dataFile.get_var("Nodes_grDfI")) )
        cleanupAndThrow(dom, "get_var(Nodes_grDfI)");
    if (! nc_var_temp->get(&nodes->globalReducedDOFIndex[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_grDfI)");
    // Nodes_grNI
    if (! ( nc_var_temp = dataFile.get_var("Nodes_grNI")) )
        cleanupAndThrow(dom, "get_var(Nodes_grNI)");
    if (! nc_var_temp->get(&nodes->globalReducedNodesIndex[0], numNodes) )
        cleanupAndThrow(dom, "get(Nodes_grNI)");
    // Nodes_Coordinates
    if (!(nc_var_temp = dataFile.get_var("Nodes_Coordinates")))
        cleanupAndThrow(dom, "get_var(Nodes_Coordinates)");
    if (! nc_var_temp->get(&nodes->Coordinates[0], numNodes, numDim) )
        cleanupAndThrow(dom, "get(Nodes_Coordinates)");

    nodes->updateTagList();

    // read elements
    const_ReferenceElementSet_ptr refElements(new ReferenceElementSet(
                (ElementTypeId)Elements_TypeId, order, reduced_order));
    ElementFile* elements = new ElementFile(refElements, mpiInfo);
    dom->setElements(elements);
    elements->allocTable(num_Elements);
    elements->minColor = 0;
    elements->maxColor = num_Elements-1;
    if (num_Elements > 0) {
       // Elements_Id
       if (! ( nc_var_temp = dataFile.get_var("Elements_Id")) )
           cleanupAndThrow(dom, "get_var(Elements_Id)");
       if (! nc_var_temp->get(&elements->Id[0], num_Elements) )
           cleanupAndThrow(dom, "get(Elements_Id)");
       // Elements_Tag
       if (! ( nc_var_temp = dataFile.get_var("Elements_Tag")) )
           cleanupAndThrow(dom, "get_var(Elements_Tag)");
       if (! nc_var_temp->get(&elements->Tag[0], num_Elements) )
           cleanupAndThrow(dom, "get(Elements_Tag)");
       // Elements_Owner
       if (! ( nc_var_temp = dataFile.get_var("Elements_Owner")) )
           cleanupAndThrow(dom, "get_var(Elements_Owner)");
       if (! nc_var_temp->get(&elements->Owner[0], num_Elements) )
           cleanupAndThrow(dom, "get(Elements_Owner)");
       // Elements_Color
       if (! ( nc_var_temp = dataFile.get_var("Elements_Color")) )
           cleanupAndThrow(dom, "get_var(Elements_Color)");
       if (! nc_var_temp->get(&elements->Color[0], num_Elements) )
           cleanupAndThrow(dom, "get(Elements_Color)");
       // Now we need to adjust maxColor
       index_t mc = elements->Color[0];
       for (index_t i = 1; i < num_Elements; ++i) {
           if (mc < elements->Color[i]) {
               mc = elements->Color[i];
           }
       }
       elements->maxColor = mc;
       // Elements_Nodes
       int* Elements_Nodes = new int[num_Elements*num_Elements_numNodes];
       if (!(nc_var_temp = dataFile.get_var("Elements_Nodes"))) {
           delete[] Elements_Nodes;
           cleanupAndThrow(dom, "get_var(Elements_Nodes)");
       }
       if (! nc_var_temp->get(&Elements_Nodes[0], num_Elements, num_Elements_numNodes) ) {
           delete[] Elements_Nodes;
           cleanupAndThrow(dom, "get(Elements_Nodes)");
       }

       // Copy temp array into elements->Nodes
       for (index_t i = 0; i < num_Elements; i++) {
           for (int j = 0; j < num_Elements_numNodes; j++) {
               elements->Nodes[INDEX2(j,i,num_Elements_numNodes)]
                    = Elements_Nodes[INDEX2(j,i,num_Elements_numNodes)];
           }
       }
       delete[] Elements_Nodes;
    } // num_Elements > 0
    elements->updateTagList();

    // get the face elements
    const_ReferenceElementSet_ptr refFaceElements(
            new ReferenceElementSet((ElementTypeId)FaceElements_TypeId,
                order, reduced_order));
    ElementFile* faces = new ElementFile(refFaceElements, mpiInfo);
    dom->setFaceElements(faces);
    faces->allocTable(num_FaceElements);
    faces->minColor = 0;
    faces->maxColor = num_FaceElements-1;
    if (num_FaceElements > 0) {
        // FaceElements_Id
        if (! ( nc_var_temp = dataFile.get_var("FaceElements_Id")) )
            cleanupAndThrow(dom, "get_var(FaceElements_Id)");
        if (! nc_var_temp->get(&faces->Id[0], num_FaceElements) )
            cleanupAndThrow(dom, "get(FaceElements_Id)");
        // FaceElements_Tag
        if (! ( nc_var_temp = dataFile.get_var("FaceElements_Tag")) )
            cleanupAndThrow(dom, "get_var(FaceElements_Tag)");
        if (! nc_var_temp->get(&faces->Tag[0], num_FaceElements) )
            cleanupAndThrow(dom, "get(FaceElements_Tag)");
        // FaceElements_Owner
        if (! ( nc_var_temp = dataFile.get_var("FaceElements_Owner")) )
            cleanupAndThrow(dom, "get_var(FaceElements_Owner)");
        if (! nc_var_temp->get(&faces->Owner[0], num_FaceElements) )
            cleanupAndThrow(dom, "get(FaceElements_Owner)");
        // FaceElements_Color
        if (! ( nc_var_temp = dataFile.get_var("FaceElements_Color")) )
            cleanupAndThrow(dom, "get_var(FaceElements_Color)");
        if (! nc_var_temp->get(&faces->Color[0], num_FaceElements) )
            cleanupAndThrow(dom, "get(FaceElements_Color)");
        // Now we need to adjust maxColor
        index_t mc = faces->Color[0];
        for (index_t i = 1; i < num_FaceElements; ++i) {
            if (mc < faces->Color[i]) {
                mc = faces->Color[i];
            }
        }
        faces->maxColor = mc;
        // FaceElements_Nodes
        int* FaceElements_Nodes = new int[num_FaceElements*num_FaceElements_numNodes];
        if (!(nc_var_temp = dataFile.get_var("FaceElements_Nodes"))) {
            delete[] FaceElements_Nodes;
            cleanupAndThrow(dom, "get_var(FaceElements_Nodes)");
        }
        if (! nc_var_temp->get(&(FaceElements_Nodes[0]), num_FaceElements, num_FaceElements_numNodes) ) {
            delete[] FaceElements_Nodes;
            cleanupAndThrow(dom, "get(FaceElements_Nodes)");
        }
        // Copy temp array into faces->Nodes
        for (index_t i = 0; i < num_FaceElements; i++) {
            for (int j = 0; j < num_FaceElements_numNodes; j++) {
                faces->Nodes[INDEX2(j,i,num_FaceElements_numNodes)] = FaceElements_Nodes[INDEX2(j,i,num_FaceElements_numNodes)];
            }
        }
        delete[] FaceElements_Nodes;
    } // num_FaceElements > 0
    faces->updateTagList();

    // get the Contact elements
    const_ReferenceElementSet_ptr refContactElements(
         new ReferenceElementSet((ElementTypeId)ContactElements_TypeId,
             order, reduced_order));
    ElementFile* contacts = new ElementFile(refContactElements, mpiInfo);
    dom->setContactElements(contacts);
    contacts->allocTable(num_ContactElements);
    contacts->minColor = 0;
    contacts->maxColor = num_ContactElements-1;
    if (num_ContactElements > 0) {
        // ContactElements_Id
        if (! ( nc_var_temp = dataFile.get_var("ContactElements_Id")) )
            cleanupAndThrow(dom, "get_var(ContactElements_Id)");
        if (! nc_var_temp->get(&contacts->Id[0], num_ContactElements) )
            cleanupAndThrow(dom, "get(ContactElements_Id)");
        // ContactElements_Tag
        if (! ( nc_var_temp = dataFile.get_var("ContactElements_Tag")) )
            cleanupAndThrow(dom, "get_var(ContactElements_Tag)");
        if (! nc_var_temp->get(&contacts->Tag[0], num_ContactElements) )
            cleanupAndThrow(dom, "get(ContactElements_Tag)");
        // ContactElements_Owner
        if (! ( nc_var_temp = dataFile.get_var("ContactElements_Owner")) )
            cleanupAndThrow(dom, "get_var(ContactElements_Owner)");
        if (! nc_var_temp->get(&contacts->Owner[0], num_ContactElements) )
            cleanupAndThrow(dom, "get(ContactElements_Owner)");
        // ContactElements_Color
        if (! ( nc_var_temp = dataFile.get_var("ContactElements_Color")) )
            cleanupAndThrow(dom, "get_var(ContactElements_Color)");
        if (! nc_var_temp->get(&contacts->Color[0], num_ContactElements) )
            cleanupAndThrow(dom, "get(ContactElements_Color)");
        // Now we need to adjust maxColor
        index_t mc = contacts->Color[0];
        for (index_t i = 1; i < num_ContactElements; ++i) {
            if (mc < contacts->Color[i]) {
                mc = contacts->Color[i];
            }
        }
        contacts->maxColor = mc;
        // ContactElements_Nodes
        int* ContactElements_Nodes = new int[num_ContactElements*num_ContactElements_numNodes];
        if (!(nc_var_temp = dataFile.get_var("ContactElements_Nodes"))) {
            delete[] ContactElements_Nodes;
            cleanupAndThrow(dom, "get_var(ContactElements_Nodes)");
        }
        if (! nc_var_temp->get(&ContactElements_Nodes[0], num_ContactElements, num_ContactElements_numNodes) ) {
            delete[] ContactElements_Nodes;
            cleanupAndThrow(dom, "get(ContactElements_Nodes)");
        }
        // Copy temp array into contacts->Nodes
        for (index_t i = 0; i < num_ContactElements; i++) {
            for (int j = 0; j < num_ContactElements_numNodes; j++) {
                contacts->Nodes[INDEX2(j,i,num_ContactElements_numNodes)] = ContactElements_Nodes[INDEX2(j,i,num_ContactElements_numNodes)];
            }
        }
        delete[] ContactElements_Nodes;
    } // num_ContactElements > 0
    contacts->updateTagList();

    // get the Points (nodal elements)
    const_ReferenceElementSet_ptr refPoints(new ReferenceElementSet(
                (ElementTypeId)Points_TypeId, order, reduced_order));
    ElementFile* points = new ElementFile(refPoints, mpiInfo);
    dom->setPoints(points);
    points->allocTable(num_Points);
    points->minColor = 0;
    points->maxColor = num_Points-1;
    if (num_Points > 0) {
        // Points_Id
        if (! ( nc_var_temp = dataFile.get_var("Points_Id")))
            cleanupAndThrow(dom, "get_var(Points_Id)");
        if (! nc_var_temp->get(&points->Id[0], num_Points))
            cleanupAndThrow(dom, "get(Points_Id)");
        // Points_Tag
        if (! ( nc_var_temp = dataFile.get_var("Points_Tag")))
            cleanupAndThrow(dom, "get_var(Points_Tag)");
        if (! nc_var_temp->get(&points->Tag[0], num_Points))
            cleanupAndThrow(dom, "get(Points_Tag)");
        // Points_Owner
        if (! ( nc_var_temp = dataFile.get_var("Points_Owner")))
            cleanupAndThrow(dom, "get_var(Points_Owner)");
        if (!nc_var_temp->get(&points->Owner[0], num_Points))
            cleanupAndThrow(dom, "get(Points_Owner)");
        // Points_Color
        if (! ( nc_var_temp = dataFile.get_var("Points_Color")))
            cleanupAndThrow(dom, "get_var(Points_Color)");
        if (!nc_var_temp->get(&points->Color[0], num_Points))
            cleanupAndThrow(dom, "get(Points_Color)");
        // Now we need to adjust maxColor
        index_t mc = points->Color[0];
        for (index_t i = 1; i < num_Points; ++i) {
            if (mc < points->Color[i]) {
                mc = points->Color[i];
            }
        }
        points->maxColor = mc;
        // Points_Nodes
        int* Points_Nodes = new int[num_Points];
        if (!(nc_var_temp = dataFile.get_var("Points_Nodes"))) {
            delete[] Points_Nodes;
            cleanupAndThrow(dom, "get_var(Points_Nodes)");
        }
        if (! nc_var_temp->get(&Points_Nodes[0], num_Points) ) {
            delete[] Points_Nodes;
            cleanupAndThrow(dom, "get(Points_Nodes)");
        }
        // Copy temp array into points->Nodes
        for (index_t i = 0; i < num_Points; i++) {
            points->Id[points->Nodes[INDEX2(0,i,1)]] = Points_Nodes[i];
        }
        delete[] Points_Nodes;
    } // num_Points > 0
    points->updateTagList();

    // get the tags
    if (num_Tags > 0) {
        // Temp storage to gather node IDs
        int *Tags_keys = new int[num_Tags];
        char name_temp[4096];
        int i;

        // Tags_keys
        if (! ( nc_var_temp = dataFile.get_var("Tags_keys")) ) {
            delete[] Tags_keys;
            cleanupAndThrow(dom, "get_var(Tags_keys)");
        }
        if (! nc_var_temp->get(&Tags_keys[0], num_Tags) ) {
            delete[] Tags_keys;
            cleanupAndThrow(dom, "get(Tags_keys)");
        }
        for (i=0; i<num_Tags; i++) {
          // Retrieve tag name
          sprintf(name_temp, "Tags_name_%d", i);
          if (! (attr=dataFile.get_att(name_temp)) ) {
              delete[] Tags_keys;
              stringstream msg;
              msg << "get_att(" << name_temp << ")";
              cleanupAndThrow(dom, msg.str());
          }
          boost::scoped_array<char> name(attr->as_string(0));
          delete attr;
          dom->setTagMap(name.get(), Tags_keys[i]);
        }
        delete[] Tags_keys;
    }

    // Nodes_DofDistribution
    IndexVector first_DofComponent(mpi_size+1);
    if (! (nc_var_temp = dataFile.get_var("Nodes_DofDistribution")) ) {
        cleanupAndThrow(dom, "get_var(Nodes_DofDistribution)");
    }
    if (!nc_var_temp->get(&first_DofComponent[0], mpi_size+1)) {
        cleanupAndThrow(dom, "get(Nodes_DofDistribution)");
    }

    // Nodes_NodeDistribution
    IndexVector first_NodeComponent(mpi_size+1);
    if (! (nc_var_temp = dataFile.get_var("Nodes_NodeDistribution")) ) {
        cleanupAndThrow(dom, "get_var(Nodes_NodeDistribution)");
    }
    if (!nc_var_temp->get(&first_NodeComponent[0], mpi_size+1)) {
        cleanupAndThrow(dom, "get(Nodes_NodeDistribution)");
    }
    dom->createMappings(first_DofComponent, first_NodeComponent);

    return dom->getPtr();
#else
    throw FinleyException("loadMesh: not compiled with NetCDF. Please contact your installation manager.");
#endif // ESYS_HAVE_NETCDF
}

#endif

Domain_ptr readMesh_driver(const bp::list& args)
{
    int l = len(args);
    if (l < 7) {
        throw ValueError("Insufficient arguments to readMesh_driver");
    }
    string fileName = bp::extract<string>(args[0])();
    int integrationOrder = bp::extract<int>(args[1])();
    int reducedIntegrationOrder = bp::extract<int>(args[2])();
    bool optimize = bp::extract<bool>(args[3])();
    vector<double> points;
    vector<int> tags;

    // we need to convert lists to stl vectors
    bp::list pypoints = bp::extract<bp::list>(args[4]);
    bp::list pytags = bp::extract<bp::list>(args[5]);
    int numpts = bp::extract<int>(pypoints.attr("__len__")());
    int numtags = bp::extract<int>(pytags.attr("__len__")());

    bp::object pworld = args[6];
    JMPI info;
    if (!pworld.is_none()) {
        bp::extract<SubWorld_ptr> ex(pworld);
        if (!ex.check()) {
            throw ValueError("Invalid escriptWorld parameter.");
        }
        info = ex()->getMPI();
    } else {
        info = makeInfo(MPI_COMM_WORLD);
    }
    Domain_ptr dom(FinleyDomain::read(info, fileName, integrationOrder,
                                      reducedIntegrationOrder, optimize));

    FinleyDomain* fd = dynamic_cast<FinleyDomain*>(dom.get());

    for (int i = 0; i < numpts; ++i) {
        bp::object temp = pypoints[i];
        int l = bp::extract<int>(temp.attr("__len__")());
        for (int k = 0; k < l; ++k) {
              points.push_back(bp::extract<double>(temp[k]));
        }
    }
    // bricks use up to 200 but the existing tag check will find that
    int curmax = 40;
    const TagMap& tagmap = fd->getTagMap();
    // first we work out what tags are already in use
    for (TagMap::const_iterator it = tagmap.begin(); it != tagmap.end(); ++it) {
        if (it->second > curmax) {
            curmax = it->second+1;
        }
    }

    tags.resize(numtags, -1);
    for (int i = 0; i < numtags; ++i) {
        bp::extract<int> ex_int(pytags[i]);
        bp::extract<string> ex_str(pytags[i]);
        if (ex_int.check()) {
            tags[i] = ex_int();
            if (tags[i] >= curmax) {
                curmax = tags[i]+1;
            }
        } else if (ex_str.check()) {
            string s = ex_str();
            TagMap::const_iterator it = tagmap.find(s);
            if (it != tagmap.end()) {
                // we have the tag already so look it up
                tags[i] = it->second;
            } else {
                fd->setTagMap(s, curmax);
                tags[i] = curmax;
                curmax++;
            }
        } else {
            throw FinleyException("Unable to extract tag value.");
        }
    }
    // now we need to add the dirac points
    fd->addDiracPoints(points, tags);
    return dom;
}

Domain_ptr readGmsh_driver(const bp::list& args)
{
    int l = len(args);
    if (l < 7) {
        throw ValueError("Insufficient arguments to readMesh_driver");
    }
    string fileName = bp::extract<string>(args[0])();
    int numDim = bp::extract<int>(args[1])();
    int integrationOrder = bp::extract<int>(args[2])();
    int reducedIntegrationOrder = bp::extract<int>(args[3])();
    bool optimize = bp::extract<bool>(args[4])();
    bool useMacroElements = bp::extract<bool>(args[5])();
    vector<double> points;
    vector<int> tags;

    // we need to convert lists to stl vectors
    bp::list pypoints = bp::extract<bp::list>(args[6]);
    bp::list pytags = bp::extract<bp::list>(args[7]);
    int numpts = bp::extract<int>(pypoints.attr("__len__")());
    int numtags = bp::extract<int>(pytags.attr("__len__")());
    bp::object pworld = args[8];
    JMPI info;
    if (!pworld.is_none()) {
        bp::extract<SubWorld_ptr> ex(pworld);
        if (!ex.check()) {
            throw ValueError("Invalid escriptWorld parameter.");
        }
        info = ex()->getMPI();
    } else {
        info = makeInfo(MPI_COMM_WORLD);
    }
    Domain_ptr dom(FinleyDomain::readGmsh(info, fileName, numDim,
                                     integrationOrder, reducedIntegrationOrder,
                                     optimize, useMacroElements));
    FinleyDomain* fd = dynamic_cast<FinleyDomain*>(dom.get());

    for (int i = 0; i < numpts; ++i) {
        bp::object temp = pypoints[i];
        int l = bp::extract<int>(temp.attr("__len__")());
        for (int k = 0; k < l; ++k) {
            points.push_back(bp::extract<double>(temp[k]));
        }
    }
    int curmax = 40; // bricks use up to 30
    const TagMap& tagmap = fd->getTagMap();
    // first we work out what tags are already in use
    for (TagMap::const_iterator it = tagmap.begin(); it != tagmap.end(); ++it) {
        if (it->second > curmax) {
            curmax = it->second+1;
        }
    }

    tags.resize(numtags, -1);
    for (int i = 0; i < numtags; ++i) {
        bp::extract<int> ex_int(pytags[i]);
        bp::extract<string> ex_str(pytags[i]);
        if (ex_int.check()) {
            tags[i] = ex_int();
            if (tags[i] >= curmax) {
                curmax = tags[i]+1;
            }
        } else if (ex_str.check()) {
            string s = ex_str();
            TagMap::const_iterator it = tagmap.find(s);
            if (it != tagmap.end()) {
                // we have the tag already so look it up
                tags[i] = it->second;
            } else {
                fd->setTagMap(s, curmax);
                tags[i] = curmax;
                curmax++;
            }
        } else {
            throw FinleyException("Unable to extract tag value");
        }
    }
    // now we need to add the dirac points
    fd->addDiracPoints(points, tags);
    return dom;
}

Domain_ptr brick(JMPI info, dim_t n0, dim_t n1, dim_t n2, int order,
                 double l0, double l1, double l2,
                 bool periodic0, bool periodic1, bool periodic2,
                 int integrationOrder, int reducedIntegrationOrder,
                 bool useElementsOnFace, bool useFullElementOrder,
                 bool optimize, const std::vector<double>& points,
                 const std::vector<int>& tags,
                 const std::map<std::string, int>& tagNamesToNums)
{
    Domain_ptr dom;
    if (order == 1) {
        dom = FinleyDomain::createHex8(n0, n1, n2, l0, l1, l2, periodic0,
                   periodic1, periodic2, integrationOrder,
                   reducedIntegrationOrder, useElementsOnFace, optimize, info);
    } else if (order == 2) {
        dom = FinleyDomain::createHex20(n0, n1, n2, l0, l1, l2, periodic0,
                                   periodic1, periodic2, integrationOrder,
                                   reducedIntegrationOrder, useElementsOnFace,
                                   useFullElementOrder, false, optimize, info);
    } else if (order == -1) {
        dom = FinleyDomain::createHex20(n0, n1, n2, l0, l1, l2, periodic0,
                                   periodic1, periodic2, integrationOrder,
                                   reducedIntegrationOrder, useElementsOnFace,
                                   useFullElementOrder, true, optimize, info);
    } else {
        stringstream message;
        message << "Illegal interpolation order " << order;
        throw ValueError(message.str());
    }

    FinleyDomain* fd = dynamic_cast<FinleyDomain*>(dom.get());
    fd->addDiracPoints(points, tags);
    for (TagMap::const_iterator it = tagNamesToNums.begin(); it != tagNamesToNums.end(); ++it) {
        fd->setTagMap(it->first, it->second);
    }
    fd->getPoints()->updateTagList();
    return dom;
}

Domain_ptr brick_driver(const bp::list& args)
{
    // we need to convert lists to stl vectors
    bp::list pypoints = bp::extract<bp::list>(args[15]);
    bp::list pytags = bp::extract<bp::list>(args[16]);
    int numpts = bp::extract<int>(pypoints.attr("__len__")());
    int numtags = bp::extract<int>(pytags.attr("__len__")());
    vector<double> points;
    vector<int> tags;
    tags.resize(numtags, -1);
    for (int i = 0; i < numpts; ++i) {
        bp::object temp = pypoints[i];
        int l = bp::extract<int>(temp.attr("__len__")());
        for (int k = 0; k < l; ++k) {
            points.push_back(bp::extract<double>(temp[k]));
        }
    }
    map<string, int> namestonums;
    int curmax = 40; // bricks use up to 30
    for (int i = 0; i < numtags; ++i) {
        bp::extract<int> ex_int(pytags[i]);
        bp::extract<string> ex_str(pytags[i]);
        if (ex_int.check()) {
            tags[i] = ex_int();
            if (tags[i] >= curmax) {
                curmax = tags[i]+1;
            }
        } else if (ex_str.check()) {
            string s = ex_str();
            TagMap::iterator it = namestonums.find(s);
            if (it != namestonums.end()) {
                // we have the tag already so look it up
                tags[i] = it->second;
            } else {
                namestonums[s] = curmax;
                tags[i] = curmax;
                curmax++;
            }
        } else {
            throw FinleyException("Unable to extract tag value.");
        }
    }
    bp::object pworld = args[17];
    JMPI info;
    if (!pworld.is_none()) {
        bp::extract<SubWorld_ptr> ex(pworld);
        if (!ex.check()) {
            throw ValueError("Invalid escriptWorld parameter.");
        }
        info = ex()->getMPI();
    } else {
        info = makeInfo(MPI_COMM_WORLD);
    }
    return brick(info, static_cast<dim_t>(bp::extract<float>(args[0])),
                 static_cast<dim_t>(bp::extract<float>(args[1])),
                 static_cast<dim_t>(bp::extract<float>(args[2])),
                 bp::extract<int>(args[3]), bp::extract<double>(args[4]),
                 bp::extract<double>(args[5]), bp::extract<double>(args[6]),
                 bp::extract<int>(args[7]), bp::extract<int>(args[8]),
                 bp::extract<int>(args[9]), bp::extract<int>(args[10]),
                 bp::extract<int>(args[11]), bp::extract<int>(args[12]),
                 bp::extract<int>(args[13]), bp::extract<int>(args[14]),
                 points, tags, namestonums);
}

Domain_ptr rectangle(JMPI info, dim_t n0, dim_t n1, int order,
                     double l0, double l1, bool periodic0, bool periodic1,
                     int integrationOrder, int reducedIntegrationOrder,
                     bool useElementsOnFace, bool useFullElementOrder,
                     bool optimize, const vector<double>& points,
                     const vector<int>& tags,
                     const std::map<std::string, int>& tagNamesToNums)
{
    Domain_ptr dom;
    if (order == 1) {
        dom = FinleyDomain::createRec4(n0, n1, l0, l1, periodic0, periodic1,
                                     integrationOrder, reducedIntegrationOrder,
                                     useElementsOnFace, optimize, info);
    } else if (order == 2) {
        dom = FinleyDomain::createRec8(n0, n1, l0, l1, periodic0, periodic1,
                 integrationOrder, reducedIntegrationOrder,
                 useElementsOnFace,useFullElementOrder, false, optimize, info);
    } else if (order == -1) {
        dom = FinleyDomain::createRec8(n0, n1, l0, l1, periodic0, periodic1,
                 integrationOrder, reducedIntegrationOrder,
                 useElementsOnFace, useFullElementOrder, true, optimize, info);
    } else {
        stringstream message;
        message << "Illegal interpolation order " << order;
        throw ValueError(message.str());
    }

    FinleyDomain* fd = dynamic_cast<FinleyDomain*>(dom.get());
    fd->addDiracPoints(points, tags);
    for (TagMap::const_iterator it = tagNamesToNums.begin(); it != tagNamesToNums.end(); ++it)
    {
        fd->setTagMap(it->first, it->second);
    }
    fd->getPoints()->updateTagList();
    return dom;
}

Domain_ptr rectangle_driver(const bp::list& args)
{
    // we need to convert lists to stl vectors
    bp::list pypoints = bp::extract<bp::list>(args[12]);
    bp::list pytags = bp::extract<bp::list>(args[13]);
    int numpts = bp::extract<int>(pypoints.attr("__len__")());
    int numtags = bp::extract<int>(pytags.attr("__len__")());
    vector<double> points;
    vector<int> tags;
    tags.resize(numtags, -1);
    for (int i = 0; i < numpts; ++i) {
        bp::object temp = pypoints[i];
        int l = bp::extract<int>(temp.attr("__len__")());
        for (int k = 0; k < l; ++k) {
            points.push_back(bp::extract<double>(temp[k]));
        }
    }
    TagMap tagstonames;
    int curmax = 40;
    // but which order to assign tags to names?????
    for (int i = 0; i < numtags; ++i) {
        bp::extract<int> ex_int(pytags[i]);
        bp::extract<string> ex_str(pytags[i]);
        if (ex_int.check()) {
            tags[i] = ex_int();
            if (tags[i] >= curmax) {
                curmax = tags[i]+1;
            }
        } else if (ex_str.check()) {
            string s = ex_str();
            TagMap::iterator it = tagstonames.find(s);
            if (it != tagstonames.end()) {
                // we have the tag already so look it up
                tags[i] = it->second;
            } else {
                tagstonames[s] = curmax;
                tags[i] = curmax;
                curmax++;
            }
        } else {
            throw FinleyException("Unable to extract tag value.");
        }
    }
    bp::object pworld = args[14];
    JMPI info;
    if (!pworld.is_none()) {
        bp::extract<SubWorld_ptr> ex(pworld);
        if (!ex.check()) {
            throw ValueError("Invalid escriptWorld parameter.");
        }
        info = ex()->getMPI();
    } else {
        info = makeInfo(MPI_COMM_WORLD);
    }

    return rectangle(info, static_cast<dim_t>(bp::extract<float>(args[0])),
                     static_cast<dim_t>(bp::extract<float>(args[1])),
                     bp::extract<int>(args[2]), bp::extract<double>(args[3]),
                     bp::extract<double>(args[4]), bp::extract<int>(args[5]),
                     bp::extract<int>(args[6]), bp::extract<int>(args[7]),
                     bp::extract<int>(args[8]), bp::extract<int>(args[9]),
                     bp::extract<int>(args[10]), bp::extract<int>(args[11]),
                     points, tags, tagstonames);
}

Domain_ptr meshMerge(const bp::list& meshList)
{
    // extract the meshes from meshList
    int num = bp::extract<int>(meshList.attr("__len__")());
    vector<const FinleyDomain*> meshes(num);
    for (int i = 0; i < num; ++i) {
        AbstractContinuousDomain& meshListMember = bp::extract<AbstractContinuousDomain&>(meshList[i]);
        meshes[i] = dynamic_cast<const FinleyDomain*>(&meshListMember);
    }

    // merge the meshes
    FinleyDomain* dom = FinleyDomain::merge(meshes);

    return dom->getPtr();
}

Domain_ptr glueFaces(const bp::list& meshList, double safetyFactor,
                     double tolerance, bool optimize)
{
    // merge the meshes
    Domain_ptr merged_meshes = meshMerge(meshList);

    // glue the faces
    FinleyDomain* merged = dynamic_cast<FinleyDomain*>(merged_meshes.get());
    merged->glueFaces(safetyFactor, tolerance, optimize);
    return merged_meshes;
}

Domain_ptr joinFaces(const bp::list& meshList, double safetyFactor,
                     double tolerance, bool optimize)
{
    // merge the meshes
    Domain_ptr merged_meshes = meshMerge(meshList);

    // join the faces
    FinleyDomain* merged = dynamic_cast<FinleyDomain*>(merged_meshes.get());
    merged->joinFaces(safetyFactor, tolerance, optimize);
    return merged_meshes;
}

} // namespace finley


/*
 *  python.cpp
 *
 *   Created on: 21-may-2018
 *       Author: M. El-Kebir
 */

#include <boost/scoped_array.hpp>
#include <boost/python.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <boost/python/stl_iterator.hpp>
#include <armadillo>
#include <iostream>
#include <fstream>
#include "utils.h"
#include "simulation.h"
#include "mutclonetree.h"
#include "readmatrix.h"
#include "generatemixture.h"
#include "enumeratemutationtrees.h"

namespace p = boost::python;

template<typename T>
inline
std::vector< T > py_list_to_std_vector( const boost::python::object& iterable )
{
  return std::vector< T >( boost::python::stl_input_iterator< T >( iterable ),
                          boost::python::stl_input_iterator< T >( ) );
}

void simulate(std::string outputDirectory = ".",
              p::dict args = p::dict())
{
  std::string filenameColorMap;
  int seed = 0;
  double carryingCapacity = 5e4;
  double mutFreqThreshold = 0.05;
  double migrationRate = 1e-6;
  double mutationRate = 0.1;
  double driverRate = 2e-7;
  int maxNrAnatomicalSites = 3;
  int migrationPattern = 0;
  int nrTrials = -1;
  int desiredNrMutationClusters = -1;
  int sequencingDepth = 200;
  int nrSamplesPerAnatomicalSite = 2;
  int nrSamplesPrimary = 2;
  double sequencingErrorRate = 0;
  double purity = 1;
  bool verbose = false;

  for (const std::string& key : py_list_to_std_vector<std::string>(args.keys()))
  {
    if (key == "seed")
    {
      seed = p::extract<int>(args.get(key));
    }
    else if (key == "carryingCapacity")
    {
      carryingCapacity = p::extract<double>(args.get(key));
    }
    else if (key == "mutFreqThreshold")
    {
      mutFreqThreshold = p::extract<double>(args.get(key));
    }
    else if (key == "migrationRate")
    {
      migrationRate = p::extract<double>(args.get(key));
    }
    else if (key == "mutationRate")
    {
      mutationRate = p::extract<double>(args.get(key));
    }
    else if (key == "driverRate")
    {
      driverRate = p::extract<double>(args.get(key));
    }
    else if (key == "maxNrAnatomicalSites")
    {
      maxNrAnatomicalSites = p::extract<int>(args.get(key));
    }
    else if (key == "migrationPattern")
    {
      migrationPattern = p::extract<int>(args.get(key));
    }
    else if (key == "nrTrials")
    {
      nrTrials = p::extract<int>(args.get(key));
    }
    else if (key == "desiredNrMutationClusters")
    {
      desiredNrMutationClusters = p::extract<int>(args.get(key));
    }
    else if (key == "sequencingDepth")
    {
      sequencingDepth = p::extract<int>(args.get(key));
    }
    else if (key == "nrSamplesPerAnatomicalSite")
    {
      nrSamplesPerAnatomicalSite = p::extract<int>(args.get(key));
    }
    else if (key == "nrSamplesPrimary")
    {
      nrSamplesPrimary = p::extract<int>(args.get(key));
    }
    else if (key == "sequencingErrorRate")
    {
      sequencingErrorRate = p::extract<double>(args.get(key));
    }
    else if (key == "purity")
    {
      purity = p::extract<double>(args.get(key));
    }
    else if (key == "filenameColorMap")
    {
      filenameColorMap = p::extract<std::string>(args.get(key));
    }
    else if (key == "verbose")
    {
      verbose = p::extract<bool>(args.get(key));
    }
  }

  Simulation::run(outputDirectory, filenameColorMap,
                  seed, carryingCapacity, mutFreqThreshold,
                  migrationRate, mutationRate, driverRate,
                  maxNrAnatomicalSites, migrationPattern,
                  nrTrials, desiredNrMutationClusters,
                  sequencingDepth, nrSamplesPerAnatomicalSite, nrSamplesPrimary,
                  sequencingErrorRate, purity, verbose);
}

std::string visualizeMigrationGraph(std::string filenameInGraph,
                                    std::string filenameInColorMap = "")
{
  std::ifstream inG(filenameInGraph);
  if (!inG.good())
  {
    throw std::runtime_error("Error: could not open '"
                             + filenameInGraph + "' for reading");
  }

  MigrationGraph G;
  if (!G.read(inG))
  {
    throw std::runtime_error("Error: invalid format '"
                             + filenameInGraph + "'");
  }

  StringToIntMap colorMap;
  if (!filenameInColorMap.empty())
  {
    std::ifstream inColorMap(filenameInColorMap.c_str());
    if (!inColorMap.good())
    {
      throw std::runtime_error("Error: could not open '"
                               + filenameInColorMap + "' for reading");
    }
    if (!BaseTree::readColorMap(inColorMap, colorMap))
    {
      throw std::runtime_error("Error: error parsing '"
                               + filenameInColorMap + "'");
    }
  }
  else
  {
    colorMap = G.generateColorMap();
  }

  std::stringstream ss;

  G.writeDOT(ss, colorMap);

  return ss.str();
}

std::string visualizeCloneTree(std::string filenameInTree,
                               std::string filenameInColorMap = "",
                               std::string filenameInVertexLabeling = "")
{
  std::ifstream inT(filenameInTree.c_str());
  if (!inT.good())
  {
    throw std::runtime_error("Error: could not open '"
                             + filenameInTree + "' for reading");
  }

  MutCloneTree T;
  inT >> T;

  StringToIntMap colorMap;
  if (!filenameInColorMap.empty())
  {
    std::ifstream inColorMap(filenameInColorMap.c_str());
    if (!inColorMap.good())
    {
      throw std::runtime_error("Error: could not open '"
                               + filenameInColorMap + "' for reading");
    }
    if (!BaseTree::readColorMap(inColorMap, colorMap))
    {
      throw std::runtime_error("Error: error parsing '"
                               + filenameInColorMap + "'");
    }
  }
  else
  {
    colorMap = T.generateColorMap();
  }

  std::stringstream ss;

  if (filenameInVertexLabeling.empty())
  {
    T.writeDOT(ss, colorMap);
  }
  else
  {
    std::ifstream inVertexLabeling(filenameInVertexLabeling.c_str());
    if (!inVertexLabeling.good())
    {
      throw std::runtime_error("Error: could not open '"
                               + filenameInVertexLabeling
                               + "' for reading");
    }

    StringNodeMap lPlus(T.tree());
    if (!T.readVertexLabeling(inVertexLabeling, T, lPlus))
    {
      throw std::runtime_error("Error: error parsing '"
                               + filenameInVertexLabeling + "'");
    }

    T.writeDOT(ss, lPlus, colorMap);
  }

  return ss.str();
}

void mix(const std::string& filenameInTree,
         const std::string& filenameOutTree,
         int k,
         bool partition,
         int seed = 0)
{
  std::ifstream inT(filenameInTree.c_str());
  if (!inT.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTree + "' for reading");
  }

  MutCloneTree T;
  inT >> T;

  g_rng = std::mt19937(seed);
  MutCloneTree sampledT = GenerateMixture(T).generate(k, partition);

  std::ofstream outT(filenameOutTree.c_str());
  outT << sampledT;
  outT.close();
}

void downSample(const std::string& filenameInR,
                const std::string& filenameOutR,
                int nrSamplesPerAnatomicalSite,
                int coverage,
                int seed = 0,
                double purity = 1,
                double sequencingErrorRate = 0,
                double fractionSNVs = 1)
{
  ReadMatrix R;
  std::ifstream inR(filenameInR.c_str());
  if (!inR.good())
  {
    throw std::runtime_error("Error: failed to open '" + filenameInR + "' for reading");
  }
  inR >> R;

  g_rng = std::mt19937(seed);
  ReadMatrix newR = R.downSample(nrSamplesPerAnatomicalSite,
                                 coverage,
                                 purity,
                                 sequencingErrorRate,
                                 fractionSNVs);

  std::ofstream outR(filenameOutR.c_str());
  outR << newR;
  outR.close();
}

void treeToFreqs(const std::string& filenameInTree,
                 const std::string& filenameOutFreqs)
{
  std::ifstream inT(filenameInTree.c_str());
  if (!inT.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTree + "' for reading");
  }

  MutCloneTree T;
  inT >> T;

  std::ofstream outF(filenameOutFreqs.c_str());
  outF << T.getFrequencies();
  outF.close();
}

void readsToFreqs(const std::string& filenameInReads,
                  const std::string& filenameOutFreqs,
                  double alpha)
{
  std::ifstream inR(filenameInReads.c_str());
  if (!inR.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInReads + "' for reading");
  }

  ReadMatrix R;
  inR >> R;
  inR.close();

  std::ofstream outF(filenameOutFreqs.c_str());
  outF << R.toFrequencyMatrix(alpha, 2);
  outF.close();
}

void sequence(const std::string& filenameInTree,
              const std::string& filenameOutReads,
              int sequencingDepth,
              int seed = 0,
              int ploidy = 2,
              double purity = 1.,
              double sequencingErrorRate = 0.)
{
  std::ifstream inT(filenameInTree.c_str());
  if (!inT.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTree + "' for reading");
  }

  MutCloneTree T;
  inT >> T;

  g_rng = std::mt19937(seed);

  std::ofstream outR(filenameOutReads.c_str());
  outR << T.getReads(purity, sequencingDepth, sequencingErrorRate, ploidy, true, true);
  outR.close();
}

void precluster(const std::string& filenameInFreqs,
                const std::string& filenameInClustering,
                const std::string& filenameOutFreqs)
{
  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  std::ifstream inC(filenameInClustering.c_str());
  if (!inC.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInClustering + "' for reading");
  }

  IntMatrix clustering = F.parseClustering(inC);
  inC.close();

  FrequencyMatrix newF = F.cluster(clustering);

  std::ofstream outF(filenameOutFreqs);
  outF << newF;
  outF.close();
}

void enumerate(const std::string& filenameInFreqs,
               const std::string& filenameOutTrees,
               bool spanning,
               int nrThreads,
               int timeLimit,
               bool dryRun)
{
  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  VerbosityLevel oldVerbosity = g_verbosity;
  g_verbosity = VERBOSE_NONE;

  std::ofstream outT(filenameOutTrees.c_str());

  EnumerateMutationTrees enumerate(F);
  enumerate.enumerate("",
                      nrThreads,
                      -1,
                      timeLimit,
                      spanning,
                      dryRun,
                      outT);

  g_verbosity = oldVerbosity;

  outT.close();
}

double countSpanningTrees(const std::string& filenameInFreqs,
                          int root = 0)
{
  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  EnumerateMutationTrees mutT(F);

  const Digraph& G = mutT.getAncestryGraph().G();
  lemon::DynArcLookUp<Digraph> arcLookUp(G);

  arma::mat L(F.getNrCharacters() - 1, F.getNrCharacters() - 1);

  for (int i = 0; i < F.getNrCharacters(); ++i)
  {
    for (int j = 0; j < F.getNrCharacters(); ++j)
    {
      if (i == root || j == root)
      {
        continue;
      }

      int ii = i > root ? i - 1 : i;
      int jj = j > root ? j - 1 : j;

      if (i == j)
      {
        Node v_j = mutT.getAncestryGraph().charStateToNode(j, 1);
        L(ii, jj) = lemon::countInArcs(G, v_j) - 1;
      }
      else
      {
        Node v_i = mutT.getAncestryGraph().charStateToNode(i, 1);
        Node v_j = mutT.getAncestryGraph().charStateToNode(j, 1);

        if (arcLookUp(v_i, v_j) != lemon::INVALID)
        {
          L(ii, jj) = -1.;
        }
        else
        {
          L(ii, jj) = 0.;
        }
      }
    }
  }

//  std::cout << L << std::endl;

  return arma::det(L);
}

CloneTree parseNextTree(std::istream& in)
{
  int nrEdges = -1;
  std::string line;
  std::stringstream ss;

  getline(in, line);
  ss.clear();
  ss.str(line);
  ss >> nrEdges;

  if (nrEdges < 0)
  {
    throw std::runtime_error("Error: number of edges should be nonnegative");
  }

  ss.clear();
  ss.str("");
  for (int j = 0; j < nrEdges; ++j)
  {
    getline(in, line);
    ss << line << std::endl;
  }

  CloneTree T;
  if (!T.read(ss))
    throw std::runtime_error("");

  return T;
}

void filterSCS(const std::string& filenameInInferredTrees,
               const std::string& filenameInTrueTree,
               const std::string& filenameOutTrees,
               int nrClones,
               int seed)
{
  // 0. parse true clone tree
  std::ifstream inTrueTree(filenameInTrueTree.c_str());
  if (!inTrueTree.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTrueTree + "' for reading");
  }

  MutCloneTree trueT;
  inTrueTree >> trueT;
  inTrueTree.close();

  // 1. collect clones
  std::vector<StringVector> clones;
  for (Node leaf : trueT.leafSet())
  {
    clones.push_back(StringVector());
    Node u = leaf;
    while ((u = trueT.parent(u)) != trueT.root())
    {
      clones.back().push_back(trueT.label(u));
    }
  }

  g_rng = std::mt19937(seed);
  std::shuffle(clones.begin(), clones.end(), g_rng);
  while (clones.size() > nrClones)
  {
    clones.pop_back();
  }

  // 2. parse enumerated trees
  std::ifstream inTrees(filenameInInferredTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInInferredTrees + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }
  std::stringstream ss(line);
  ss >> nrTrees;

  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  std::ofstream outTrees(filenameOutTrees);
  for (const StringVector& clone : clones)
  {
    outTrees << "#";
    for (const std::string& mut : clone)
    {
      outTrees << " " << mut;
    }
    outTrees << std::endl;
  }

  for (int i = 0; i < nrTrees; ++i)
  {
    StringPairSet diff;
    CloneTree T = parseNextTree(inTrees);

    bool ok = true;
    for (const StringVector& clone : clones)
    {
      Node u = T.getNodeByLabel(clone[0]);
      int idx = 0;
      while (u != lemon::INVALID && ok)
      {
        ok &= (idx < clone.size()) && (T.label(u) == clone[idx]);
        u = T.parent(u);
        ++idx;
      }
    }

    if (ok)
    {
      outTrees << lemon::countArcs(T.tree()) << " #edges" << std::endl;
      T.write(outTrees);
    }
  }
  outTrees.close();
}

void filterLR(const std::string& filenameInInferredTrees,
              const std::string& filenameInTrueTree,
              const std::string& filenameOutTrees,
              int nrPairs,
              int seed)
{
  // 0. parse true clone tree
  std::ifstream inTrueTree(filenameInTrueTree.c_str());
  if (!inTrueTree.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTrueTree + "' for reading");
  }

  MutCloneTree trueT;
  inTrueTree >> trueT;
  inTrueTree.close();

  // 1. collect clones
  StringPairSet ancestralPairSet = trueT.getAncestralPairsNoRoot();
  std::vector<StringPair> vec(ancestralPairSet.begin(), ancestralPairSet.end());

  g_rng = std::mt19937(seed);
  std::shuffle(vec.begin(), vec.end(), g_rng);
  while (vec.size() > nrPairs)
  {
    vec.pop_back();
  }
  ancestralPairSet = StringPairSet(vec.begin(), vec.end());

  // 2. parse enumerated trees
  std::ifstream inTrees(filenameInInferredTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInInferredTrees + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }
  std::stringstream ss(line);
  ss >> nrTrees;

  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  std::ofstream outTrees(filenameOutTrees);
  for (const StringPair& pair : ancestralPairSet)
  {
    outTrees << "# " << pair.first << " " << pair.second << std::endl;
  }

  for (int i = 0; i < nrTrees; ++i)
  {
    StringPairSet diff;
    CloneTree T = parseNextTree(inTrees);
    StringPairSet ancestralPairSetT = T.getAncestralPairs();

    std::set_difference(ancestralPairSet.begin(), ancestralPairSet.end(),
                        ancestralPairSetT.begin(), ancestralPairSetT.end(),
                        std::inserter(diff, diff.begin()));

    if (diff.empty())
    {
      outTrees << lemon::countArcs(T.tree()) << " #edges" << std::endl;
      T.write(outTrees);
    }
  }
  outTrees.close();
}

void computeRecall(const std::string& filenameInInferredTrees,
                   const std::string& filenameInTrueTree,
                   const std::string& filenameOutRecall)
{
  std::ifstream inTrueTree(filenameInTrueTree.c_str());
  if (!inTrueTree.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTrueTree + "' for reading");
  }

  CloneTree trueT;
  if (!trueT.read(inTrueTree))
  {
    throw std::runtime_error("Parse error: '" + filenameInTrueTree + "'");
  }
  inTrueTree.close();

  StringPairSet trueParental = trueT.getParentalPairs();
  StringPairSet trueAncestral = trueT.getAncestralPairs();
  StringPairSet trueIncomparable = trueT.getIncomparablePairs();

  std::ifstream inTrees(filenameInInferredTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInInferredTrees + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }

  std::stringstream ss(line);
  ss >> nrTrees;
  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  std::ofstream outRecall(filenameOutRecall);
  outRecall << "solution\tancestral\tparental\tincomparable" << std::endl;
  for (int treeIdx = 0; treeIdx < nrTrees; ++treeIdx)
  {
    CloneTree T = parseNextTree(inTrees);
    StringPairSet inferredParental = T.getParentalPairs();
    StringPairSet inferredAncestral = T.getAncestralPairs();
    StringPairSet inferredIncomparable = T.getIncomparablePairs();

    outRecall << treeIdx << "\t"
              << BaseTree::recall(inferredAncestral, trueAncestral)
              << "\t"
              << BaseTree::recall(inferredParental, trueParental)
              << "\t"
              << BaseTree::recall(inferredIncomparable, trueIncomparable)
              << std::endl;
  }
  inTrees.close();
  outRecall.close();
}

double getFractionOfIncomparablePairs(const std::string& filenameInFreqs)
{
  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  return EnumerateMutationTrees(F).getAncestryGraph().fracOfIncomparablePairs();
}

void summarize(const std::string& filenameInAllTrees,
               const std::string& filenameInSampledTree,
               const std::string& filenameOutSummary)
{
  // 1. Parse sampled trees
  std::map<StringPairSet, int> sampledTreeHistogram;

  std::ifstream inTrees(filenameInSampledTree.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInSampledTree + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }

  std::stringstream ss(line);
  ss >> nrTrees;
  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  for (int treeIdx = 0; treeIdx < nrTrees; ++treeIdx)
  {
    CloneTree T = parseNextTree(inTrees);
    StringPairSet edges;
    for (ArcIt a(T.tree()); a != lemon::INVALID; ++a)
    {
      Node u = T.tree().source(a);
      Node v = T.tree().target(a);

      edges.insert(StringPair(T.label(u), T.label(v)));
    }
    if (sampledTreeHistogram.count(edges) == 0)
    {
      sampledTreeHistogram[edges] = 1;
    }
    else
    {
      ++sampledTreeHistogram[edges];
    }
  }
  inTrees.close();

  // 2. Parse all trees
  std::map<StringPairSet, int> allTreeIndex;

  inTrees = std::ifstream(filenameInAllTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInAllTrees + "' for reading");
  }

  nrTrees = -1;
  line.clear();
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }

  ss.clear();
  ss.str(line);
  ss >> nrTrees;
  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  for (int treeIdx = 0; treeIdx < nrTrees; ++treeIdx)
  {
    CloneTree T = parseNextTree(inTrees);
    StringPairSet edges;
    for (ArcIt a(T.tree()); a != lemon::INVALID; ++a)
    {
      Node u = T.tree().source(a);
      Node v = T.tree().target(a);

      edges.insert(StringPair(T.label(u), T.label(v)));
    }
    if (allTreeIndex.count(edges) > 0)
    {
      throw std::runtime_error("Error: duplicate tree");
    }

    allTreeIndex[edges] = treeIdx;
  }
  inTrees.close();

  std::ofstream outSum(filenameOutSummary);
  int incorrect = 0;
  outSum << "index\tcount" << std::endl;
  for (const auto& kv : sampledTreeHistogram)
  {
    if (allTreeIndex.count(kv.first) == 0)
    {
      outSum << --incorrect << "\t" << kv.second << std::endl;
    }
    else
    {
      outSum << allTreeIndex[kv.first] << "\t" << kv.second << std::endl;
    }
  }
  for (const auto& kv : allTreeIndex)
  {
    if (sampledTreeHistogram.count(kv.first) == 0)
    {
      outSum << kv.second << "\t" << 0 << std::endl;
    }
  }
  outSum.close();
}

int rejectionSample(const std::string& filenameInFreqs,
                    const std::string& filenameOutTrees,
                    int count,
                    int seed = 0)
{
  g_rng = std::mt19937(seed);

  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  const int n = F.getNrCharacters();
  const int k = F.getNrSamples();

  RealTensor FF(2, k, n);
  for (int p = 0; p < k; ++p)
  {
    FF.setRowLabel(p, F.indexToSample(p));
    for (int i = 0; i < n; ++i)
    {
      if (p == 0)
      {
        FF.setColLabel(i, F.indexToCharacter(i));
      }
      FF.set(1, p, i, F.min(p, i));
      FF.set(0, p, i, 1 - F.max(p, i));
    }
  }

  StateTreeVector S(F.getNrCharacters(), StateTree({-1, 0}));

  gm::RootedCladisticAncestryGraph G(FF, S);
  G.init();

  std::ofstream outTrees(filenameOutTrees);
  outTrees << count << " #trees" << std::endl;
  StringPairSet tree;
  int sampleCount = 0;
  while (count > 0)
  {
    ++sampleCount;
    if (G.sample(tree))
    {
      outTrees << tree.size() << " #edges" << std::endl;
      for (const StringPair& edge : tree)
      {
        outTrees << edge.first << " " << edge.second << std::endl;
      }
      --count;
    }
  }

  return sampleCount;
}

std::string visualizeEnumeratedCloneTree(const std::string& filenameInFreqs,
                                         const std::string& filenameInTrees,
                                         int index,
                                         bool showCharacterLabels = false,
                                         int offset = 1,
                                         bool showFrequencies = false)
{
  std::ifstream inF(filenameInFreqs.c_str());
  if (!inF.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInFreqs + "' for reading");
  }

  FrequencyMatrix F;
  inF >> F;
  inF.close();

  std::ifstream inTrees(filenameInTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTrees + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }

  std::stringstream ss(line);
  ss >> nrTrees;
  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  if (!(0 <= index && index < nrTrees))
  {
    throw std::runtime_error("Error: invalid index");
  }

  std::stringstream ssOut;
  for (int treeIdx = 0; treeIdx < nrTrees; ++treeIdx)
  {
    CloneTree T = parseNextTree(inTrees);
    if (index == treeIdx)
    {
      ssOut << "digraph T_" << index << " {" << std::endl;

      for (NodeIt v(T.tree()); v != lemon::INVALID; ++v)
      {
        ssOut << "  " << T.tree().id(v) << " [label=\"";

        const std::string& label_v = T.label(v);
        if (showCharacterLabels)
          ssOut << label_v;
        else
          ssOut << F.characterToIndex(label_v) + offset;

        if (showFrequencies)
        {
          for (int p = 0; p < F.getNrSamples(); ++p)
          {
            ssOut << "\\n" << F.min(p, F.characterToIndex(label_v)) << " " << F.max(p, F.characterToIndex(label_v));
          }
        }

        ssOut << "\"]" << std::endl;
      }

      for (ArcIt a(T.tree()); a != lemon::INVALID; ++a)
      {
        Node u = T.tree().source(a);
        Node v = T.tree().target(a);

        ssOut << "  " << T.tree().id(u) << " -> " << T.tree().id(v) << std::endl;
      }

      ssOut << "}" << std::endl;
      break;
    }
  }
  inTrees.close();

  return ssOut.str();
}

int identifySolution(const std::string& filenameInTrees,
                     const std::string& filenameInTree)
{
  std::ifstream inTrueTree(filenameInTree.c_str());
  if (!inTrueTree.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTree + "' for reading");
  }

  CloneTree trueT;
  if (!trueT.read(inTrueTree))
  {
    throw std::runtime_error("Parse error: '" + filenameInTree + "'");
  }
  inTrueTree.close();
  StringPairSet edgesTrueT = trueT.getEdgeSet();

  std::ifstream inTrees(filenameInTrees.c_str());
  if (!inTrees.good())
  {
    throw std::runtime_error("Error: could not open '" + filenameInTrees + "' for reading");
  }

  int nrTrees = -1;
  std::string line;
  while (line.empty() || line[0] == '#')
  {
    getline(inTrees, line);
  }

  std::stringstream ss(line);
  ss >> nrTrees;
  if (nrTrees < 0)
  {
    throw std::runtime_error("Error: number of trees should be nonnegative");
  }

  int res = -1;
  for (int treeIdx = 0; treeIdx < nrTrees; ++treeIdx)
  {
    CloneTree T = parseNextTree(inTrees);
    if (T.getEdgeSet() == edgesTrueT)
    {
      res = treeIdx;
      break;
    }
  }
  inTrees.close();

  return res;
}

BOOST_PYTHON_FUNCTION_OVERLOADS(sequence_overloads, sequence, 3, 7);

BOOST_PYTHON_FUNCTION_OVERLOADS(downSample_overloads, downSample, 4, 8);

BOOST_PYTHON_FUNCTION_OVERLOADS(visualizeCloneTree_overloads, visualizeCloneTree, 1, 3);

BOOST_PYTHON_FUNCTION_OVERLOADS(visualizeEnumeratedCloneTree_overloads, visualizeEnumeratedCloneTree, 3, 6);

BOOST_PYTHON_FUNCTION_OVERLOADS(visualizeMigrationGraph_overloads, visualizeMigrationGraph, 1, 2);

BOOST_PYTHON_FUNCTION_OVERLOADS(simulate_overloads, simulate, 0, 2);

BOOST_PYTHON_FUNCTION_OVERLOADS(countSpanningTrees_overloads, countSpanningTrees, 1, 2);

BOOST_PYTHON_FUNCTION_OVERLOADS(mix_overloads, mix, 4, 5);

BOOST_PYTHON_FUNCTION_OVERLOADS(rejectionSample_overloads, rejectionSample, 3, 4);

BOOST_PYTHON_MODULE(oncolib)
{
  p::def("sequence", sequence,
         sequence_overloads(p::args("filenameInTree",
                                    "filenameOutReads",
                                    "sequencingDepth",
                                    "seed=0",
                                    "ploidy=2",
                                    "purity=1.",
                                    "sequencingErrorRate=0."),
                            "Generate NGS reads from clone tree"));

  p::def("simulate", simulate, simulate_overloads(p::args("outputDirectory='.'",
                                                          "args={'filenameColorMap:'', "\
                                                          "'seed':0, "\
                                                          "'carryingCapacity':5e4, "\
                                                          "'mutFreqThreshold':0.05, "\
                                                          "'migrationRate':1e-6, "\
                                                          "'mutationRate':0.1, "\
                                                          "'driverRate':2e-7, "\
                                                          "'maxNrAnatomicalSites':3, "\
                                                          "'migrationPattern':0, "\
                                                          "'nrTrials':-1, "\
                                                          "'desiredNrMutationClusters':-1, "
                                                          "'sequencingDepth':200, "\
                                                          "'nrSamplesPerAnatomicalSite':2, "\
                                                          "'nrSamplesPrimary':2, "\
                                                          "'sequencingErrorRate':0, "\
                                                          "'purity':1., "\
                                                          "'verbose':False}"),
                                                  "Simulate a metastatic tumor"));

  p::def("tree2dot", visualizeCloneTree,
         visualizeCloneTree_overloads(p::args("filenameInTree",
                                              "filenameInColorMap=''",
                                              "filenameInVertexLabeling=''"),
                                      "Visualize clone tree in DOT format"));

  p::def("enumeratedtree2dot", visualizeEnumeratedCloneTree,
         visualizeEnumeratedCloneTree_overloads(p::args("filenameInFreqs",
                                                        "filenameInTrees",
                                                        "index",
                                                        "showCharacterLabels=False",
                                                        "offset=1",
                                                        "showFrequencies=False"),
                                                "Visualize enumerated clone tree in DOT format"));

  p::def("graph2dot", visualizeMigrationGraph,
         visualizeMigrationGraph_overloads(p::args("filenameInTree",
                                                   "filenameInColorMap=''"),
                                      "Visualize clone tree in DOT format"));

  p::def("downsample", downSample,
         downSample_overloads(p::args("filenameInR",
                                      "filenameOutR",
                                      "nrSamplesPerAnatomicalSite",
                                      "coverage",
                                      "seed=0",
                                      "purity=1.",
                                      "sequencingErrorRate=0.",
                                      "fractionSNVs=1."),
                              "Down sample reads"));

  p::def("mix", mix, mix_overloads(p::args("filenameInTree",
                                           "filenameOutTree",
                                           "k",
                                           "partition",
                                           "seed=0"),
                                   "Generate mixture of clone tree leaves"));

  p::def("tree2freqs", treeToFreqs, "Extract mutation frequencies from clone tree");

  p::def("reads2freqs", readsToFreqs, "Extract mutation frequencies from reads");

  p::def("precluster", precluster, "Cluster mutation frequencies given clustering");

  p::def("enumerate", enumerate, "Enumerate mutation trees");

  p::def("countSpanningTrees", countSpanningTrees,
         countSpanningTrees_overloads(p::args("filenameInF",
                                              "root=0"),
                                      "Compute number of spanning arborescence in ancestry graph"));

  p::def("computeRecall", computeRecall, "Compute recall");

  p::def("getFractionOfIncomparablePairs", getFractionOfIncomparablePairs,
         "Compute fraction of incomparable pairs");

  p::def("filterSCS", filterSCS, "Include SCS information");

  p::def("filterLR", filterLR, "Include long read information");

  p::def("summarize", summarize, "Compute histogram of sampled trees w.r.t. all trees");

  p::def("identifySolution", identifySolution, "Determine whether specified tree occurs in specified trees");

  p::def("rejectionSample", rejectionSample,
         rejectionSample_overloads(p::args("filenameInFreqs",
                                           "filenameOutTrees",
                                           "count",
                                           "seed=0"),
                                   "Rejection sampling of spanning trees satisfying SC"));
}


#include <cstdlib>
#include <memory>
#include <algorithm>
#include <complex>
#include <iostream>
#include <fstream>
#include <map>
#include <utility>
#include <vector>
#include <numeric>
#if defined(USE_MPI)
#include <mpi.h>
#endif

#include <boost/version.hpp>
#include "hdf/hdf_multi.h"
#include "hdf/hdf_archive.h"

#include "AFQMC/config.h"
#include "AFQMC/Hamiltonians/HamiltonianFactory.h"
#include "AFQMC/Hamiltonians/HamiltonianFactory_Helper.h"

#include "AFQMC/Hamiltonians/THCHamiltonian.h"
#include "AFQMC/Hamiltonians/FactorizedSparseHamiltonian.h"
#include "AFQMC/Hamiltonians/KPFactorizedHamiltonian.h"
#include "AFQMC/Hamiltonians/RealDenseHamiltonian.h"
#include "AFQMC/Hamiltonians/RealDenseHamiltonian_v2.h"
//#include "AFQMC/Hamiltonians/KPTHCHamiltonian.h"

#include "AFQMC/Utilities/readHeader.h"
#include "AFQMC/Utilities/Utils.hpp"

#include "AFQMC/Numerics/ma_operations.hpp"
#include "AFQMC/Matrix/csr_matrix.hpp"

#include "AFQMC/Matrix/hdf5_readers.hpp"
#include "AFQMC/Utilities/hdf5_consistency_helper.hpp"
#include "AFQMC/Matrix/array_partition.hpp"

namespace qmcplusplus
{
namespace afqmc
{
Hamiltonian HamiltonianFactory::fromHDF5(GlobalTaskGroup& gTG, xmlNodePtr cur)
{
  if (cur == NULL)
    APP_ABORT("Error: NULL xml pointer in HamiltonianFactory::parse(). \n");

  std::string info("info0");
  OhmmsAttributeSet oAttrib;
  oAttrib.add(info, "info");
  oAttrib.put(cur);

  if (InfoMap.find(info) == InfoMap.end())
  {
    app_error() << "ERROR: Undefined info in execute block. \n";
    APP_ABORT("ERROR: Undefined info in execute block. \n");
  }

  AFQMCInfo& AFinfo = InfoMap[info];

  int NMO  = AFinfo.NMO;
  int NAEA = AFinfo.NAEA;
  int NAEB = AFinfo.NAEB;

  // defaults
  double cutoff1bar    = 1e-8;
  std::string fileName = "";
  int number_of_TGs    = 1;
  int n_reading_cores  = -1;
  std::string alt      = "";

  ParameterSet m_param;
  m_param.add(cutoff1bar, "cutoff_1bar", "double");
  m_param.add(fileName, "filename", "std::string");
  m_param.add(number_of_TGs, "nblocks", "int");
  m_param.add(n_reading_cores, "num_io_cores", "int");
  m_param.add(alt, "alternate", "std::string");
  m_param.put(cur);

  // make or get TG
  number_of_TGs  = std::max(1, std::min(number_of_TGs, gTG.getTotalNodes()));
  TaskGroup_& TG = getTG(gTG, number_of_TGs);

  // processor info
  int ncores = TG.getTotalCores(), coreid = TG.getCoreID();
  int nread = (n_reading_cores <= 0) ? (ncores) : (std::min(n_reading_cores, ncores));
  int head  = TG.getGlobalRank() == 0;

  app_log() << " Initializing Hamiltonian from file: " << fileName << std::endl;

  // FIX FIX FIX
  hdf_archive dump(TG.Global());
  // these cores will read from hdf file
  if (coreid < nread)
  {
    if (!dump.open(fileName, H5F_ACC_RDONLY))
    {
      app_error() << " Error opening integral file in SparseGeneralHamiltonian. \n";
      APP_ABORT("");
    }
    if (!dump.push("Hamiltonian", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group not Hamiltonian found. \n";
      APP_ABORT("");
    }
  }

  HamiltonianTypes htype = UNKNOWN;
  if (head)
    htype = peekHamType(dump);
  {
    int htype_ = int(htype);
    TG.Global().broadcast_n(&htype_, 1, 0);
    htype = HamiltonianTypes(htype_);
  }

  int complex_integrals;
  // Hamiltonian file may not contain flag.
  bool have_complex_flag = true;
  if (head)
  {
    if (!dump.readEntry(complex_integrals, "ComplexIntegrals"))
    {
      have_complex_flag = false;
    }
  }
  TG.Global().broadcast_n(&have_complex_flag, 1, 0);
  if (have_complex_flag && head)
  {
#ifdef QMC_COMPLEX
    if (!complex_integrals)
      app_log() << " Note: Found real integrals with QMC_COMPLEX=1.\n";
#else
    if (complex_integrals)
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Found complex integrals but QMC_COMPLEX=0.\n";
      app_error() << " Please build QMCPACK with complex support or write real integrals if appropriate.\n";
      APP_ABORT("");
    }
#endif
  }

  int int_blocks, nvecs;
  std::vector<int> Idata(8);
  if (head)
    if (!dump.readEntry(Idata, "dims"))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Problems reading dims. \n";
      APP_ABORT("");
    }
  TG.Global().broadcast(Idata.begin(), Idata.end());

  int_blocks = Idata[2];
  if (Idata[3] != NMO)
  {
    app_error() << " ERROR: NMO differs from value in integral file. \n";
    APP_ABORT(" Error: NMO differs from value in integral file. \n");
  }
  if (Idata[4] != NAEA)
  {
    app_log() << " WARNING: NAEA differs from value in integral file. \n";
    //      APP_ABORT(" ");
  }
  if (Idata[5] != NAEB)
  {
    app_log() << " WARNING: NAEB differs from value in integral file. \n";
    //      APP_ABORT(" ");
  }
  nvecs = Idata[7];
#ifdef QMC_COMPLEX
  int nkpts = -1;
  if (htype == KPFactorized || htype == KPTHC)
    nkpts = Idata[2];
#endif

  // MAM: this is wrong in NONCOLLINEAR, but how do I know what
  // walker type it is right here???
  // Might need to read dimensions ahead of time from hdf5 file and check consistensy
  // later
  // Also, OneBodyHamiltonian doesn't make much sense now that you have KP classes.
  // Consider refactoring this part of the code...
  // It is not really used now, you can just read H1 in Sparse class too...

  // 1 body hamiltonian: Why isn't this in shared memory!!!
  boost::multi::array<ValueType, 2> H1({NMO, NMO});

  ValueType NuclearCoulombEnergy(0);
  ValueType FrozenCoreEnergy(0);

  if (head)
  {
    std::vector<RealType> Rdata(2);
    if (!dump.readEntry(Rdata, "Energies"))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Problems reading  dataset. \n";
      APP_ABORT(" ");
    }
    if (Rdata.size() > 0)
      NuclearCoulombEnergy = Rdata[0];
    if (Rdata.size() > 1)
      FrozenCoreEnergy = Rdata[1];
  }

  TG.Global().broadcast_n(&NuclearCoulombEnergy, 1, 0);
  TG.Global().broadcast_n(&FrozenCoreEnergy, 1, 0);

  if (head)
  {
    using ma::conj;
    using std::imag;
    bool foundH1 = false;
#ifdef QMC_COMPLEX
    if (htype == KPFactorized || htype == KPTHC)
    {
#else
    if (htype == RealDenseFactorized)
    {
#endif
      // nothing to do, H1 is read during construction of HamiltonianOperations object.
    }
    else
    {
      if (readComplexOrReal(dump, "hcore", H1)) {}
      else
      {
        app_log() << " Reading one-body Hamiltonian in sparse format.\n";
        H1.reextent({NMO, NMO});
        if (Idata[0] < 1)
        {
          app_error() << " Error in HamiltonianFactory::fromHDF5(): Dimensions of H1 < 1.  \n";
          APP_ABORT(" ");
        }

        std::vector<OrbitalType> ivec(2 * Idata[0]);
        if (!dump.readEntry(ivec, "H1_indx"))
        {
          app_error() << " Error in HamiltonianFactory::fromHDF5(): Problems reading H1_indx. \n";
          APP_ABORT(" ");
        }
        std::vector<ValueType> vvec(Idata[0]);
        if (!readComplexOrReal(dump, "H1", vvec))
        {
          app_error() << " Error in HamiltonianFactory::fromHDF5(): Problems reading H1.  \n";
          APP_ABORT(" ");
        }

        for (int i = 0; i < Idata[0]; i++)
        {
          // keep i<=j by default
          if (ivec[i] <= ivec[i])
          {
            H1[ivec[2 * i]][ivec[2 * i + 1]] = vvec[i];
            H1[ivec[2 * i + 1]][ivec[2 * i]] = ma::conj(vvec[i]);
          }
          else
          {
            H1[ivec[2 * i]][ivec[2 * i + 1]] = ma::conj(vvec[i]);
            H1[ivec[2 * i + 1]][ivec[2 * i]] = vvec[i];
          }
        }
      }
      app_log() << " Successfully read one-body Hamiltonian.\n";
      app_log() << " Shape of one-body Hamiltonian: (" << NMO << ", " << NMO << ")." << std::endl;
    }
  }
  TG.Global().broadcast_n(to_address(H1.origin()), H1.num_elements(), 0);

  // now read the integrals
#ifdef QMC_COMPLEX
  if (htype == KPTHC)
  {
    APP_ABORT(" Error: KPTHC hamiltonian not yet working. \n");
    if (coreid < nread && !dump.push("KPTHC", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group not KPTHC found. \n";
      APP_ABORT("");
    }
    if (coreid < nread)
    {
      dump.pop();
      dump.pop();
      dump.close();
    }
    TG.global_barrier();
    return Hamiltonian{};
    //      return Hamiltonian(KPTHCHamiltonian(AFinfo,cur,std::move(H1),TG,
    //                                        NuclearCoulombEnergy,FrozenCoreEnergy));
  }
  else if (htype == KPFactorized)
  {
    if (coreid < nread && !dump.push("KPFactorized", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group not KPFactorized found. \n";
      APP_ABORT("");
    }
    if (coreid < nread)
    {
      dump.pop();
      dump.pop();
      dump.close();
    }
    TG.global_barrier();
    // KPFactorizedHamiltonian matrices are read by THCHamiltonian object when needed,
    // since their ownership is passed to the HamOps object.
    return Hamiltonian(KPFactorizedHamiltonian(AFinfo, cur, std::move(H1), TG, NuclearCoulombEnergy, FrozenCoreEnergy));
  }
  else
#else
  if (htype == RealDenseFactorized)
  {
    if (coreid < nread && !dump.push("DenseFactorized", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group not DenseFactorized found. \n";
      APP_ABORT("");
    }
    if (coreid < nread)
    {
      dump.pop();
      dump.pop();
      dump.close();
    }
    TG.global_barrier();
    // KPFactorizedHamiltonian matrices are read by THCHamiltonian object when needed,
    // since their ownership is passed to the HamOps object.
#if defined(ENABLE_CUDA) || defined(ENABLE_HIP)
    //      if(alt == "yes" || alt == "true")
    //        return Hamiltonian(RealDenseHamiltonian(AFinfo,cur,std::move(H1),TG,
    //                                        NuclearCoulombEnergy,FrozenCoreEnergy));
    //      else
    return Hamiltonian(RealDenseHamiltonian_v2(AFinfo, cur, std::move(H1), TG, NuclearCoulombEnergy, FrozenCoreEnergy));
#else
    return Hamiltonian(RealDenseHamiltonian(AFinfo, cur, std::move(H1), TG, NuclearCoulombEnergy, FrozenCoreEnergy));
#endif
  }
  else
#endif
      if (htype == THC)
  {
    if (coreid < nread && !dump.push("THC", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group not THC found. \n";
      APP_ABORT("");
    }
    if (coreid < nread)
    {
      dump.pop();
      dump.pop();
      dump.close();
    }
    TG.global_barrier();
    // THC matrices are read by THCHamiltonian object when needed, since their ownership is
    // passed to the HamOps object.
    return Hamiltonian(THCHamiltonian(AFinfo, cur, std::move(H1), TG, NuclearCoulombEnergy, FrozenCoreEnergy));
  }
  else if (htype == Factorized)
  {
    if (coreid < nread && !dump.push("Factorized", false))
    {
      app_error() << " Error in HamiltonianFactory::fromHDF5(): Group Factorized not found. \n";
      APP_ABORT("");
    }

    if (TG.getNumberOfTGs() > 1)
      APP_ABORT(" Error: Distributed Factorized hamiltonian not yet implemented. \n\n");

    FactorizedSparseHamiltonian::shm_csr_matrix V2_fact =
        read_V2fact(dump, TG, nread, NMO, nvecs, cutoff1bar, int_blocks);

    app_log() << " Memory used by factorized 2-el integral table (on head node): "
              << (V2_fact.capacity() * (sizeof(ValueType) + sizeof(IndexType)) +
                  V2_fact.size(0) * (2 * sizeof(std::size_t))) /
            1024.0 / 1024.0
              << " MB. " << std::endl;

    if (coreid < nread)
    {
      dump.pop();
      dump.pop();
      dump.close();
    }
    TG.global_barrier();

    return Hamiltonian(FactorizedSparseHamiltonian(AFinfo, cur, std::move(H1), std::move(V2_fact), TG,
                                                   NuclearCoulombEnergy, FrozenCoreEnergy));
  }

  app_error() << " Error in HamiltonianFactory::fromHDF5(): Unknown Hamiltonian Type. \n";
  APP_ABORT("");
  return Hamiltonian{};
}
} // namespace afqmc
} // namespace qmcplusplus