/*****************************************************************************
*
* Copyright (c) 2003-2020 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-2017 by Centre for Geoscience Computing (GeoComp)
* Development from 2019 by School of Earth and Environmental Sciences
**
*****************************************************************************/

#include "Data.h"
#include "DataVector.h"
#include "FileWriter.h"
#include "Utils.h"

#include <cstring>
#include <fstream>
#ifndef _WIN32
# include <unistd.h>
#endif // _WIN32

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

#ifdef ESYS_HAVE_BOOST_NUMPY
#include <boost/python/numpy.hpp>
#include "DataTypes.h"
#endif

namespace bp = boost::python;

#ifndef OVERLORDPATH
#define OVERLORDPATH ""
#endif

namespace escript {

int getSvnVersion()
{
#ifdef SVN_VERSION
    return SVN_VERSION;
#else
    return 0;
#endif
}

// This is probably not very robust, but it works on Savanna today and is
// useful for performance analysis
int get_core_id()
{
    int processor_num=-1;
#ifdef CORE_ID1
    FILE *fp;
    int i, count_spaces=0;
    char fname[100];
    char buf[1000];

    sprintf(fname, "/proc/%d/stat", getpid());
    fp = fopen(fname, "r");
    if (fp == NULL) return(-1);
    fgets(buf, 1000, fp);
    fclose(fp);

    for (i=strlen(buf)-1; i>=0; i--) {
        if (buf[i] == ' ') count_spaces++;
        if (count_spaces == 4) break;
    }
    processor_num = atoi(&buf[i+1]);
#endif
    return processor_num;
}

void setNumberOfThreads(int num_threads)
{
#ifdef _OPENMP
    omp_set_num_threads(num_threads);
#endif
}

int getNumberOfThreads()
{
#ifdef _OPENMP
    return omp_get_max_threads();
#else
    return 1;
#endif
}

int getMPISizeWorld()
{
    int mpi_num = 1;
#ifdef ESYS_MPI
    MPI_Comm_size(MPI_COMM_WORLD, &mpi_num);
#endif
    return mpi_num;
}

int getMPIRankWorld()
{
    int mpi_iam = 0;
#ifdef ESYS_MPI
    MPI_Comm_rank(MPI_COMM_WORLD, &mpi_iam);
#endif
    return mpi_iam;
}

int getMPIWorldMax(int val)
{
#ifdef ESYS_MPI
    int val2 = val;
    int out = val;
    MPI_Allreduce(&val2, &out, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD);
#else
    int out = val;
#endif
    return out;
}

int getMPIWorldSum(int val)
{
#ifdef ESYS_MPI
    int val2 = val;
    int out = 0;
    MPI_Allreduce(&val2, &out, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
#else
    int out = val;
#endif
    return out;
}

void printParallelThreadCnt()
{
    char hname[64];

#ifdef HAVE_GETHOSTNAME
    gethostname(hname, 64);
    hname[63] = '\0';
#else
    strcpy(hname, "unknown host");
#endif

    const int mpi_num = getMPISizeWorld();
    const int mpi_iam = getMPIRankWorld();

#pragma omp parallel
    {
        int omp_iam=0, omp_num=1;
#ifdef _OPENMP
        omp_iam = omp_get_thread_num();
        omp_num = omp_get_num_threads();
#endif
#pragma omp critical (printthrdcount)
        printf("printParallelThreadCounts: MPI=%03d/%03d OpenMP=%03d/%03d "
               "running on %s core %d\n", mpi_iam, mpi_num, omp_iam, omp_num,
               hname, get_core_id());
    }
}

#define CHILD_FAIL 2
#define CHILD_COMPLETE 4

#ifndef _WIN32
#ifdef ESYS_MPI
#include <sys/socket.h>
#include <sys/select.h>
#include <errno.h>
#include <arpa/inet.h>
int prepareSocket(unsigned short *port, int *key)
{
    if (getMPIRankWorld() != 0)
        return 0;
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket creation failure");
        return -1;
    }
    int opt = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) < 0) {
        perror("socket option setting failure");
        close(sfd);
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(0);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));

    if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind failure");
        close(sfd);
        return -1;
    }

    if (listen(sfd, SOMAXCONN) < 0) {
        perror("listen failure");
        close(sfd);
        return -1;
    }

    struct sockaddr actual;
    unsigned int size = sizeof(actual);
    if (getsockname(sfd, &actual, &size) < 0) {
        perror("failed when determining bound port number");
        close(sfd);
        return -1;
    }

    //if size > sizeof(actual), some info was truncated, but certainly not port

    *port = ntohs(((struct sockaddr_in *) &actual)->sin_port);

    unsigned int seed = time(NULL) % UINT_MAX;
    *key = rand_r(&seed);
    return sfd;
}

int check_data(unsigned int max, fd_set *all, fd_set *valid, int key, int sfd)
{
    int ret = 0;
    for (int i = 0; i <= max; i++) {
        if (i == sfd)
            continue;
        if (FD_ISSET(i, all)) {
            int provided = 0;
            if (recv(i, &provided, sizeof(int), MSG_WAITALL) == sizeof(int)
                    && provided == key) {
                char deadspace[1024];
                while ((provided = recv(i, deadspace, 1024, 0))) {
                    if (provided == -1) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            continue;
                        } else {
                            perror("connection failure");
                            return CHILD_FAIL;
                        }
                    }
                }
                return CHILD_COMPLETE;
            } else {
                FD_CLR(i, all);
                close(i);
            }
        }
    }
    return ret;
}

void close_all(unsigned int maxfd, fd_set *all)
{
    for (int i = 0; i <= maxfd; i++) {
        if (FD_ISSET(i, all))
            close(i);
    }
}

int waitForCompletion(int sfd, int key)
{
    if (getMPIRankWorld() != 0)
        return 0;
    int timeout = 10; //max of 10 seconds with no communication

    fd_set all, valid;
    FD_ZERO(&all);
    FD_ZERO(&valid);
    FD_SET(sfd, &all);
    time_t last_good_time = time(NULL);
    unsigned int maxfd = sfd;
    while (time(NULL) - last_good_time < timeout) {
        struct timeval timer = {1,0}; //1 sec, 0 usec
        int count = select(maxfd + 1, &all, NULL, NULL, &timer);
        if (count == -1) { //error
            if (errno == EINTR) {
                continue; //just a signal, continue as we were
            } else {
                perror("socket operation error");
                close(sfd);
                return -1;
            }
        } else if (FD_ISSET(sfd, &all)) { //new connection
            int connection = accept(sfd, NULL, NULL);
            if (connection > maxfd)
                maxfd = connection;
            FD_SET(connection, &all);
            FD_CLR(connection, &valid);
            time(&last_good_time);
            count--;
        }
        if (count > 0) { //something to read, either connection key or state
            int res = check_data(maxfd, &all, &valid, key, sfd);
            if (res == CHILD_FAIL) {
                return -1;
            } else if (res == CHILD_COMPLETE) {
                close_all(maxfd, &all);
                return 0;
            }
        }
    }
    close_all(maxfd, &all);
    fprintf(stderr, "Connection to child process timed out\n");
    return -1;
}
#endif //ESYS_MPI
#endif //not _WIN32

int runMPIProgram(bp::list args)
{
#ifdef ESYS_MPI
    unsigned short port = 0;
    int key = 0;
    int sock = prepareSocket(&port, &key);
    if (getMPIWorldSum(sock) < 0)
        return -1;
    char portstr[20] = {'\0'}, keystr[20] = {'\0'};
    sprintf(portstr, "%d", port);
    sprintf(keystr, "%d", key);

    int nargs = bp::extract<int>(args.attr("__len__")());
    // make room for port, key and terminator
    char** c_args = new char*[nargs + 3];
    c_args[0] = portstr;
    c_args[1] = keystr;
    std::vector<std::string> cpp_args(nargs);
    // skip command name in argument list
    for (int i = 0; i < nargs; i++) {
        cpp_args[i] = bp::extract<std::string>(args[i]);
        c_args[i+2] = const_cast<char*>(cpp_args[i].c_str());
    }
    c_args[nargs+2] = NULL;

    MPI_Info info;
    MPI_Info_create(&info);
    // force the gmsh process to run on this host as well for network comm
    char hostname[MPI_MAX_PROCESSOR_NAME];
    int temp = MPI_MAX_PROCESSOR_NAME;
    MPI_Get_processor_name(hostname, &temp);
    char hoststr[] = "host"; //for warnings
    MPI_Info_set(info, hoststr, hostname);
    MPI_Comm intercomm;
    int errors;
    char c_cmd[] = OVERLORDPATH"escript-overlord";
    MPI_Comm_spawn(c_cmd, c_args, 1, info, 0, MPI_COMM_WORLD, &intercomm, &errors);
    MPI_Info_free(&info);
    delete[] c_args;
    if (errors != MPI_SUCCESS)
        return errors;
    return getMPIWorldMax(waitForCompletion(sock, key));
#else //#ifdef ESYS_MPI
    std::string cmd;
    int nargs = bp::extract<int>(args.attr("__len__")());
    for (int i = 0; i < nargs; i++) {
        cmd += bp::extract<std::string>(args[i]);
        cmd += " ";
    }
    return system(cmd.c_str());
#endif //#ifdef ESYS_MPI/else
}
#undef CHILD_COMPLETE
#undef CHILD_FAIL

double getMachinePrecision()
{
    return std::numeric_limits<double>::epsilon();
}

double getMaxFloat()
{
    return std::numeric_limits<double>::max();
}

void MPIBarrierWorld()
{
#ifdef ESYS_MPI
    if (!NoCOMM_WORLD::active()) {
        MPI_Barrier(MPI_COMM_WORLD );
    } else {
        throw EsysException("Attempt to use MPI_COMM_WORLD while it is blocked.");
    }
#endif
}

void saveDataCSV(const std::string& filename, bp::dict arg,
                 const std::string& sep, const std::string& csep, bool refid, bool append)
{
    bp::list keys = arg.keys();
    int numdata = bp::extract<int>(arg.attr("__len__")());

    bool hasmask = arg.has_key("mask");
    Data mask;
    if (hasmask) {
        mask = bp::extract<escript::Data>(arg["mask"]);
        if (mask.getDataPointRank() != 0) {
            throw DataException("saveDataCSV: mask must be scalar.");
        }
        keys.remove("mask");
        numdata--;
    }
    if (numdata < 1) {
        throw DataException("saveDataCSV: no data to save specified.");
    }

    std::vector<int> step(numdata);
    std::vector<std::string> names(numdata);
    std::vector<Data> data(numdata);
    std::vector<int> fstypes(numdata+int(hasmask)); // FunctionSpace types for each data for interpolation

    keys.sort(); // to get some predictable order to things

    // We need to interpret the samples correctly even if they are different
    // types. For this reason, we should iterate over samples...
    for (int i=0; i<numdata; ++i) {
        names[i] = bp::extract<std::string>(keys[i]);
        data[i] = bp::extract<escript::Data>(arg[keys[i]]);
        fstypes[i] = data[i].getFunctionSpace().getTypeCode();
        step[i] = (data[i].actsExpanded() ? DataTypes::noValues(data[i].getDataPointShape()) : 0);

        if (i > 0) {
            if (data[i].getDomain()!=data[i-1].getDomain()) {
                throw DataException("saveDataCSV: all data must be on the same domain.");
            }
        }
        if (data[i].isComplex()) {
            throw DataException("saveDataCSV: complex values must be separated into components before calling this.");
        }
    }

    if (hasmask) {
        if (mask.getDomain() != data[0].getDomain())
            throw DataException("saveDataCSV: mask domain must be the same as the data domain.");
        fstypes[numdata] = mask.getFunctionSpace().getTypeCode();
    }

    int bestfnspace = 0;
    if (!data[0].getDomain()->commonFunctionSpace(fstypes, bestfnspace)) {
        throw DataException("saveDataCSV: FunctionSpaces of data are incompatible");
    }

    // now we interpolate all data to the same type
    FunctionSpace best(data[0].getDomain(), bestfnspace);
    for (int i=0; i<numdata; ++i) {
        data[i] = data[i].interpolate(best);
    }
    if (hasmask)
        mask = mask.interpolate(best);

    // these must be the same for all data
    int numsamples = data[0].getNumSamples();
    int dpps = data[0].getNumDataPointsPerSample();
    std::ostringstream os;
    bool first = true;

    if (data[0].getDomain()->getMPIRank() == 0) {
        for (int i=0; i<numdata; ++i) {
            const DataTypes::ShapeType& s=data[i].getDataPointShape();
            switch (data[i].getDataPointRank()) {
                case 0:
                    if (!first) {
                        os << sep;
                    } else {
                        first=false;
                        if(refid){
                            os << "Ref_ID" << sep;
                        }
                    }
                    os << names[i];
                    break;

                case 1:
                    for (int j=0; j<s[0]; ++j) {
                        if (!first) {
                            os << sep;
                        } else {
                            first=false;
                            if(refid){
                                os << "Ref_ID" << sep;
                            }
                        }
                        os << names[i] << csep << j;
                    }
                    break;

                case 2:
                    for (int j=0; j<s[0]; ++j) {
                        for (int k=0; k<s[1]; ++k) {
                            if (!first) {
                                os << sep;
                            } else {
                                first=false;
                                if(refid){
                                    os << "Ref_ID" << sep;
                                }
                            }
                            os << names[i] << csep << k << csep << j;
                        }
                    }
                    break;

                case 3:
                    for (int j=0; j<s[0]; ++j) {
                        for (int k=0; k<s[1]; ++k) {
                            for (int l=0; l<s[2]; ++l) {
                                if (!first) {
                                    os << sep;
                                } else {
                                    first=false;
                                    if(refid){
                                        os << "Ref_ID" << sep;
                                    }
                                }
                                os << names[i] << csep << k << csep << j
                                   << csep << l;
                            }
                        }
                    }
                    break;

                case 4:
                    for (int j=0; j<s[0]; ++j) {
                        for (int k=0; k<s[1]; ++k) {
                            for (int l=0; l<s[2]; ++l) {
                                for (int m=0; m<s[3]; ++m) {
                                    if (!first) {
                                        os << sep;
                                    } else {
                                        first=false;
                                        if(refid){
                                            os << "Ref_ID" << sep;
                                        }
                                    }
                                    os << names[i] << csep << k << csep << j
                                       << csep << l << csep << m;
                                }
                            }
                        }
                    }
                    break;

                default:
                    throw DataException("saveDataCSV: Illegal rank");
            }
        }
        os << std::endl;
    }

    const double* masksample = NULL;

    // does the mask act expanded?
    // Are there mask value for each point in the sample?
    bool expandedmask = false;
    // do we output this row?
    bool wantrow = true;
    if (hasmask && mask.actsExpanded()) {
        expandedmask=true;
    }
    os.setf(std::ios_base::scientific, std::ios_base::floatfield);
    os.precision(15);

    // errors prior to this point will occur on all processes anyway
    // so there is no need to explicitly notify other ranks
    int error = 0;
    std::string localmsg;
    try {
        std::vector<int> offset(numdata);
        std::vector<const DataTypes::real_t*> samples(numdata);
#ifdef ESYS_MPI
        // Loop over the MPI ranks
        for(int token = 0; token < data[0].getDomain()->getMPISize(); token++)
        {
            data[0].getDomain()->MPIBarrier();
            if(data[0].getDomain()->getMPIRank() != token)
                continue;
#endif

	    	const DataTypes::real_t onlyreal=0;
            for (int i=0; i<numsamples; ++i) {
                if (!best.ownSample(i)) {
                    continue;
                }
                wantrow = true;
                for (int d=0; d<numdata; ++d) {
                    samples[d] = data[d].getSampleDataRO(i, onlyreal);
                }
                if (hasmask) {
                    masksample = mask.getSampleDataRO(i, onlyreal);
                    if (!expandedmask) {
                        // mask controls whole sample
                        if (masksample[0] <= 0) {
                            wantrow = false;
                        }
                    }
                }
                for (int j=0; j<dpps; ++j) {
                    // now we need to check if this point is masked off
                    if (expandedmask) {
                        // masks are scalar so the relevant value is at [j]
                        wantrow = (masksample[j]>0);
                    }
                    if (wantrow) {
                        bool needsep = false;
                        // If necessary, write the element id to the output stream
                        if(refid){
                            os << best.getReferenceIDOfSample(i) << sep;
                        }
                        for (int d = 0; d < numdata; ++d) {
                            DataTypes::pointToStream(os, samples[d],
                                data[d].getDataPointShape(), offset[d], needsep, sep);
                            needsep = true;
                            offset[d] += step[d];
                        }
                        os << std::endl;
                    }
                }
                for (int d = 0; d<numdata; d++) {
                    offset[d]=0;
                }
            }
#ifdef ESYS_MPI
        }
#endif
    } catch (EsysException* e) {
        error=1;
        if (data[0].getDomain()->getMPISize()==1) {
            throw;
        } else {
            localmsg=e->what();
        }
    } catch (...) {
        error=1;
        if (data[0].getDomain()->getMPISize()==1) {
            throw;
        }
    }

#ifdef ESYS_MPI
    MPI_Comm com = data[0].getDomain()->getMPIComm();
    int rerror = 0;
    MPI_Allreduce(&error, &rerror, 1, MPI_INT, MPI_MAX, com);
    error = rerror;
#else
    MPI_Comm com = MPI_COMM_NULL;
#endif

    if (error) {
        if (localmsg.empty()) {
            throw DataException("saveDataCSV: error building output");
        }
        throw DataException(std::string("saveDataCSV:")+localmsg);
    }

    // at this point os will contain the text to be written
    FileWriter fw(com);
    if (!fw.openFile(filename, 0, false, append)) {
        throw DataException("saveDataCSV: unable to open file for writing");
    }
    error = !fw.writeOrdered(os);
    fw.close();

    data[0].getDomain()->MPIBarrier();

    if (error)
        throw DataException("saveDataCSV: Error writing to file");
}

#ifdef ESYS_HAVE_BOOST_NUMPY
boost::python::list getNumpy(boost::python::dict arg)
{
    // Initialise boost python numpy
    bp::numpy::initialize();

    // Extract the key information and the number of data ppoints
    bp::list keys = arg.keys();
    int numdata = bp::extract<int>(arg.attr("__len__")());

    // Process the mask information
    bool hasmask = arg.has_key("mask");
    Data mask;
    if (hasmask) {
        mask = bp::extract<escript::Data>(arg["mask"]);

        // Possible error: Mask is not scalar
        if (mask.getDataPointRank() != 0) {
            throw DataException("getNumpy: mask must be scalar.");
        }

        keys.remove("mask");
        numdata--;
    }

    // Possible error: User forgot to pass some data
    if (numdata < 1) {
        throw DataException("getNumpy: no data to save specified.");
    }

    // Initialise vectors
    std::vector<int> step(numdata);
    std::vector<std::string> names(numdata);
    std::vector<Data> data(numdata);
    std::vector<int> fstypes(numdata+int(hasmask)); // FunctionSpace types for each data for interpolation

    // We need to interpret the samples correctly even if they are different
    // types. For this reason, we should iterate over samples...
    for (int i = 0; i < numdata; ++i) {
        names[i] = bp::extract<std::string>(keys[i]);
        data[i] = bp::extract<escript::Data>(arg[keys[i]]);
        fstypes[i] = data[i].getFunctionSpace().getTypeCode();
        step[i] = (data[i].actsExpanded() ? DataTypes::noValues(data[i].getDataPointShape()) : 0);

        // Possible error: The data are on different domains
        if (i > 0) {
            if (data[i].getDomain()!=data[i-1].getDomain()) {
                throw DataException("getNumpy: all data must be on the same domain.");
            }
        }
    }

    // Check for complex data
    bool have_complex = false;
    for (int i = 0; i < numdata; ++i){
        if(data[i].isComplex()){
            have_complex = true;
        }
    }

    if (hasmask) {
        //Possible error: Mask domain is different to the data domain
        if (mask.getDomain() != data[0].getDomain())
            throw DataException("getNumpy: mask domain must be the same as the data domain.");

        // Get the mask fs code
        fstypes[numdata] = mask.getFunctionSpace().getTypeCode();
    }

    // Possible error: Functionspaces are incompatible so interpolation isn't possible
    int bestfnspace = 0;
    if (!data[0].getDomain()->commonFunctionSpace(fstypes, bestfnspace)) {
        throw DataException("getNumpy: FunctionSpaces of data are incompatible");
    }

    // Interpolate the data onto the same function space
    FunctionSpace best(data[0].getDomain(), bestfnspace);
    for (int i=0; i<numdata; ++i) {
        data[i] = data[i].interpolate(best);
    }
    if (hasmask)
        mask = mask.interpolate(best);

    // This are needed below
    const DataTypes::real_t onlyreal = 0;
    const DataTypes::cplx_t gotcomplex = 0.0;

    // Work out how big the ndarrays have to be
    int arraylength = 0;
    int numsamples = data[0].getNumSamples();
    int dpps = data[0].getNumDataPointsPerSample();
    if(hasmask){
        #pragma omp parallel for
        for(int i = 0; i < numsamples * dpps; i++){
            arraylength += (bool) *mask.getSampleDataRO(i, onlyreal);
        }
    } else {
        arraylength = dpps * numsamples;
    }

    //Work out how many rows each array should have
    std::vector<int> spaces(numdata);
    signed int total = 0;
    spaces[0] = 0;
    for(int i = 0; i < numdata; i++){
        total += data[i].getShapeProduct();
        spaces[i+1] = total;
    }

    // Initialise the numpy ndarray
    bp::tuple arrayshape = bp::make_tuple(total, arraylength);
    bp::numpy::dtype datatype = bp::numpy::dtype::get_builtin<double>();
    if(have_complex){
        datatype = bp::numpy::dtype::get_builtin<std::complex<double>>();
    }
    bp::numpy::ndarray dataArray = bp::numpy::zeros(arrayshape, datatype);

    // Initialise variables
    const double* masksample = NULL;
    bool expandedmask = false;              // does the mask act expanded?
    bool wantrow = true;                    // do we output this row?
    if (hasmask && mask.actsExpanded()) {
        expandedmask=true;
    }

    // Make everything more complicated.
    if(have_complex){
        for(int i = 0; i < numdata; i++){
            if(!data[i].isComplex()){
                data[i].complicate();
            }
        }
    }

    int error = 0;
    int maskcounter = 0;
    std::string localmsg;
    try {
        std::vector<int> offset(numdata);
        std::vector<const DataTypes::real_t*> samplesR(numdata);
        std::vector<const DataTypes::cplx_t*> samplesC(numdata);

        for (int i = 0; i < numsamples; ++i) {

            // If this MPI process does not own the sample then continue
            if (!best.ownSample(i)) {
                continue;
            }

            // Get the sample data
            for (int d = 0; d < numdata; ++d) {
                // if(have_complex){
                if(data[d].isComplex()){
                    samplesC[d] = data[d].getSampleDataRO(i, gotcomplex);
                } else {
                    samplesR[d] = data[d].getSampleDataRO(i, onlyreal);
                }
            }

            // Work out if we want to get this row
            wantrow = true;
            if (hasmask) {
                masksample = mask.getSampleDataRO(i, onlyreal);
                if (!expandedmask) {
                    // mask controls whole sample
                    if (masksample[0] <= 0) {
                        wantrow = false;
                    }
                }
            }

            // Loop over data points in the sample
            for (int j = 0; j < dpps; ++j) {

                // now we need to check if this point is masked off
                if (expandedmask) {
                    // masks are scalar so the relevant value is at [j]
                    wantrow = (masksample[j]>0);
                }

                // If we want the row then add it to the array
                if (wantrow) {
                    for (int d = 0; d < numdata; ++d) {

                        if(have_complex){
                            DataTypes::pointToNumpyArray(dataArray, samplesC[d],
                                data[d].getDataPointShape(), offset[d], spaces[d], hasmask ? maskcounter : i+j*numsamples);
                        } else {

                            DataTypes::pointToNumpyArray(dataArray, samplesR[d],
                                data[d].getDataPointShape(), offset[d], spaces[d], hasmask ? maskcounter : i+j*numsamples);
                        }

                        offset[d] += step[d];
                        maskcounter++;
                    }
                }
            }

            // Reset
            for (int d = 0; d < numdata; d++) {
                offset[d] = 0;
            }

        }
    } catch (EsysException* e) {
        error=1;
        if (data[0].getDomain()->getMPISize()==1) {
            throw;
        } else {
            localmsg=e->what();
        }
    } catch (...) {
        error=1;
        if (data[0].getDomain()->getMPISize()==1) {
            throw;
        }
    }

#ifdef ESYS_MPI
    MPI_Comm com = data[0].getDomain()->getMPIComm();
    int rerror = 0;
    MPI_Allreduce(&error, &rerror, 1, MPI_INT, MPI_MAX, com);
    error = rerror;
#endif

    if (error) {
        if (localmsg.empty()) {
            throw DataException("getNumpy: error building output");
        }
        throw DataException(std::string("getNumpy:")+localmsg);
    }

    // MPI Barrier
    data[0].getDomain()->MPIBarrier();

    // Other, unknown errors
    if (error)
        throw DataException("getNumpy: Unknown error.");

    // Put everything into a list to return to python
    bp::list answer;
    for(int i = 0; i < numdata; i++){
        bp::numpy::ndarray temp = bp::numpy::zeros(bp::make_tuple(data[i].getShapeProduct(), arraylength), datatype);
        for(int j = 0; j < data[i].getShapeProduct(); j++){
            temp[j] = dataArray[spaces[i]+j];
        }
        answer.append(names[i]);
        answer.append(temp);
    }

    // Print out the ndarray to the console - used during debugging
    // std::cout << "Finished array:\n" << bp::extract<char const *>(bp::str(dataArray)) << std::endl;

    return answer;
}
#else
void getNumpy(bp::dict arg){
    throw DataException("getNumpy: Error - Please recompile escripts with the boost numpy library");
}
#endif

#ifdef ESYS_HAVE_BOOST_NUMPY
boost::python::numpy::ndarray convertToNumpy(escript::Data data)
{
    // Initialise boost numpy
    // Py_Initialize();
    boost::python::numpy::initialize();

    // Check to see if we have complex data
    bool have_complex = data.isComplex();

    // Work out how many data points there are
    int numDataPoints = data.getNumSamples();
    int dpps = data.getNumDataPointsPerSample();

    // Work out the data point shape
    std::vector<int> shape = data.getDataPointShape();
    if(shape.size() == 0){ // If we have scalar data, the shape will be ()
        shape.push_back(1);
    }

    // Work out the shape
    int dimensions = data.getShapeProduct();

    // Initialise the ndarray
    boost::python::tuple arrayshape = boost::python::make_tuple(dimensions, dpps * numDataPoints);
    boost::python::numpy::dtype datatype = boost::python::numpy::dtype::get_builtin<double>();
    if (have_complex) {
        datatype = boost::python::numpy::dtype::get_builtin<std::complex<double>>();
    }
    boost::python::numpy::ndarray dataArray = boost::python::numpy::zeros(arrayshape, datatype);

    // Initialise variables
    std::string localmsg;
    std::vector<const DataTypes::real_t*> samplesR(1);

    // This is needed below in getSampleDataRO
    const DataTypes::real_t onlyreal = 0;
    const DataTypes::cplx_t onlycomplex = 0;

// #pragma omp parallel for
    for (int i = 0; i < numDataPoints; ++i) {
        for (int j = 0; j < shape[0]; j++) {
            if(have_complex){
                dataArray[j][i] = *(data.getSampleDataRO(i, onlycomplex)+j);
            } else {
                dataArray[j][i] = *(data.getSampleDataRO(i, onlyreal)+j);
            }
        }
    }

    // Print out the ndarray to the console - used during debugging
    // std::cout << "Finished array:\n" << bp::extract<char const *>(bp::str(dataArray)) << std::endl;

    return dataArray;
}
#else
void convertToNumpy(escript::Data data){
    throw DataException("getNumpy: Error - Please recompile escripts with the boost numpy library");
}
#endif

void resolveGroup(bp::object obj)
{
    int len=0;
    try {
        len=bp::extract<int>(obj.attr("__len__")());
    }
    catch(...)
    {
        // tell python the error isn't there anymore
        PyErr_Clear();
        throw DataException("resolveGroup: sequence object expected.");
    }
    std::vector<DataLazy*> dats;
    std::vector<Data*> dp;
    for (int i=0; i<len; ++i) {
        Data* p=0;
        try {
            p = bp::extract<Data*>(obj[i]);
        } catch(...) {
            PyErr_Clear();
            throw DataException("resolveGroup: only accepts Data objects.");
        }
        if (p->isLazy()) {
            dats.push_back(dynamic_cast<DataLazy*>(p->borrowData()));
            dp.push_back(p);
        }
    }
    if (!dats.empty()) {
        dats[0]->resolveGroupWorker(dats);
    }

    // all the data will be identities now but still lazy
    // convert it to ready
    for (int i=dp.size()-1; i>=0; --i)
        dp[i]->resolve();
}

} // end of namespace


// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_ASTRONOMY_IO_CARD_HPP
#define BOOST_ASTRONOMY_IO_CARD_HPP

#include <string>
#include <sstream>

#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/type.hpp>

#include <boost/astronomy/exception/fits_exception.hpp>

namespace boost { namespace astronomy { namespace io {

//!structure to store a card (80 byte key value pairs as well as comments and history cards)
struct card
{
private:
    std::string card_;

public:
    card()
    {
        this->card_.reserve(80);
    }
    //! creating card from const char*
    //! it will read 80 char from provided pointer
    card(char const* c) : card()
    {
        this->card_.assign(c, 80);
    }

    //!a string is expected with lenght no more than 80 chars
    //!this string will be directly stored in the card
    //!string must follow all the standerd of the key, value and comment for card
    card(std::string str) : card()
    {
        if (str.length() > 80)
        {
            throw invalid_card_length_exception();
        }
        this->card_ = str.append(80 - str.length(), ' ');
    }

    //!key, value and optional comments are expected
    //!all the values will be directly stored as provided so necessary spaces befor values must be provided
    //!spaces after the keyword and value will be taken care implicitly
    card
    (
        std::string const& key,
        std::string const& value,
        std::string const& comment = ""
    )
    {
        if (key.length() > 8)
        {
            throw invalid_key_length_exception();
        }
        if ((comment.length() > 0) && (value.length() + comment.length() > 68))
        {
            throw invalid_value_length_exception();
        }
        else if (value.length() > 70)
        {
            throw invalid_value_length_exception();
        }

        if (comment.length())
        {
            this->card_ = std::string(key).append(8 - key.length(), ' ') +
                "= " + value + " /" + comment +
                std::string("").append(68 - value.length() + comment.length(), ' ');
        }
        else
        {
            this->card_ = std::string(key).append(8 - key.length(), ' ') + "= " +
                std::string(value).append(70 - key.length(), ' ');
        }
    }

    //!this overload supports date and string types
    //!key, value and optional comments are expected
    //!all the values will be directly stored as provided so necessary spaces befor values must be provided
    //!spaces after the keyword and value will be taken care implicitly
    void create_card
    (
        std::string const& key,
        std::string const& value,
        std::string const& comment = ""
    )
    {
        if (key.length() > 8)
        {
            throw invalid_key_length_exception();
        }
        if ((comment.length() > 0) && (value.length() + comment.length() > 68))
        {
            throw invalid_value_length_exception();
        }
        else if (value.length() > 70)
        {
            throw invalid_value_length_exception();
        }

        if (comment.length())
        {
            this->card_ = std::string(key).append(8 - key.length(), ' ') +
                "= " + value + " /" + comment +
                std::string("").append(68 - value.length() + comment.length(), ' ');
        }
        else
        {
            this->card_ = std::string(key).append(8 - key.length(), ' ') +
                "= " + std::string(value).append(70 - key.length(), ' ');
        }
    }

    //!create card with boolean value
    void create_card(std::string const& key, bool value, std::string const& comment = "")
    {
        if (value)
        {
            create_card(key, std::string("T").insert(0, ' ', 19), comment);
        }
        else
        {
            create_card(key, std::string("F").insert(0, ' ', 19), comment);
        }
    }

    //!create card with numeric value
    template <typename Value>
    void create_card(std::string const& key, Value value, std::string const& comment = "")
    {
        std::ostringstream stream;
        stream << value;

        std::string val = stream.str();
        val.insert(0, ' ', 20 - val.length());
        create_card(key, val, comment);
    }

    //!create card for complex value
    template <typename Real, typename Imaginary>
    void create_card
    (
        std::string const& key,
        Real real,
        Imaginary imaginary,
        std::string const& comment = ""
    )
    {
        std::ostringstream stream;
        stream << real << ", " << imaginary;

        std::string value = "(" + stream.str() + ")";

        create_card(key, value, comment);
    }

    //! to create comment, history or balnk keyword cards
    //! value will be put from column 11
    void create_commentary_card(std::string const& key, std::string const& value)
    {
        if (key.length() > 8)
        {
            throw invalid_key_length_exception();
        }
        if (value.length() > 70)
        {
            throw invalid_value_length_exception();
        }

        this->card_ = std::string(key).append(8 - key.length(), ' ') +
            "  " + std::string(value).append(70 - key.length(), ' ');
    }

    //!if whole value is set to true then string is returned with trailing spaces
    std::string key(bool whole = false) const
    {
        if (whole)
        {
            return this->card_.substr(0, 8);
        }
        return boost::algorithm::trim_copy(this->card_.substr(0, 8));
    }

    /*!
    return types can be int, float, double, bool, string
    (date and complex numbers are returned as string surrounded in single quotes or in brackets)
    */
    template <typename ReturnType>
    ReturnType value() const
    {
        return value_imp(boost::type<ReturnType>());
    }

    //!returns value portion of card with comment as std::string
    std::string value_with_comment() const
    {
        return this->card_.substr(10);
    }

    //!set value of current card
    void value(std::string const& value)
    {
        if (this->key().length() == 0)
        {
            throw key_not_defined_exception();
        }
        if (value.length() > 70)
        {
            throw invalid_value_length_exception();
        }
        this->card_.append(70 - value.length(), ' ');
    }

private:

    template <typename ReturnType>
    ReturnType value_imp(boost::type<ReturnType>) const
    {
        std::string val = boost::algorithm::trim_copy(
            this->card_.substr(10, this->card_.find('/') - 10));
        return boost::lexical_cast<ReturnType>(val);
    }

    bool value_imp(boost::type<bool>) const
    {
        std::string val = boost::algorithm::trim_copy(
            this->card_.substr(10, this->card_.find('/') - 10));
        if (val == "T")
        {
            return true;
        }
        return false;
    }

};

}}} //namespace boost
#endif // !BOOST_ASTRONOMY_IO_CARD_HPP


//---------------------------------------------------------------------------//
//!
//! \file   Data_ElectronPhotonRelaxationDataContainer.hpp
//! \author Alex Robinson, Luke Kersting
//! \brief  The native electron-photon-relaxation data container class decl.
//!
//---------------------------------------------------------------------------//

#ifndef DATA_ELECTRON_PHOTON_RELAXATION_DATA_CONTAINER_HPP
#define DATA_ELECTRON_PHOTON_RELAXATION_DATA_CONTAINER_HPP

// Std Lib Includes
#include <vector>
#include <set>
#include <map>
#include <utility>
#include <string>

// Boost Includes
#include <boost/serialization/split_member.hpp>

// FRENSIE Includes
#include "Utility_StandardArchivableObject.hpp"
#include "Utility_StandardSerializableObject.hpp"

namespace Data{

/*! The electron-photon-relaxation data container
 * \details Linear-linear interpolation should be used for all data.
 */
class ElectronPhotonRelaxationDataContainer : public Utility::StandardArchivableObject<ElectronPhotonRelaxationDataContainer,false>, public Utility::StandardSerializableObject<ElectronPhotonRelaxationDataContainer,false>
{

public:

  //! Constructor (from saved archive)
  ElectronPhotonRelaxationDataContainer(
		  const std::string& archive_name,
                  const Utility::ArchivableObject::ArchiveType archive_type =
		  Utility::ArchivableObject::XML_ARCHIVE );

  //! Destructor
  virtual ~ElectronPhotonRelaxationDataContainer()
  { /* ... */ }

//---------------------------------------------------------------------------//
// GET RELAXATION DATA
//---------------------------------------------------------------------------//

  //! Return the atomic number
  unsigned getAtomicNumber() const;

  //! Return the atomic subshells
  const std::set<unsigned>& getSubshells() const;

  //! Return the occupancy for a subshell
  double getSubshellOccupancy( const unsigned subshell ) const;

  //! Return the binding energy for a subshell
  double getSubshellBindingEnergy( const unsigned subshell ) const;

  //! Return if there is relaxation data
  bool hasRelaxationData() const;

  //! Return if the subshell has relaxation data
  bool hasSubshellRelaxationData( const unsigned subshell ) const;

  //! Return the number of transitions that can fill a subshell vacancy
  unsigned getSubshellRelaxationTransitions( const unsigned subshell ) const;

  //! Return the relaxation vacancies for a subshell
  const std::vector<std::pair<unsigned,unsigned> >&
  getSubshellRelaxationVacancies( const unsigned subshell ) const;

  //! Return the relaxation particle energies for a subshell
  const std::vector<double>& getSubshellRelaxationParticleEnergies(
					       const unsigned subshell ) const;

  //! Return the relaxation probabilities for a subshell
  const std::vector<double>& getSubshellRelaxationProbabilities(
					       const unsigned subshell ) const;

//---------------------------------------------------------------------------//
// GET PHOTON DATA
//---------------------------------------------------------------------------//

  //! Return the Compton profile momentum grid
  const std::vector<double>& getComptonProfileMomentumGrid(
					       const unsigned subshell ) const;

  //! Return the Compton profile for a subshell
  const std::vector<double>& getComptonProfile(const unsigned subshell ) const;

  //! Return the occupation number momentum grid
  const std::vector<double>& getOccupationNumberMomentumGrid(
					       const unsigned subshell ) const;

  //! Return the occupation number for a subshell
  const std::vector<double>& getOccupationNumber(
					       const unsigned subshell ) const;

  //! Return the Waller-Hartree scattering function momentum grid
  const std::vector<double>&
  getWallerHartreeScatteringFunctionMomentumGrid() const;

  //! Return the Waller-Hartree scattering function
  const std::vector<double>& getWallerHartreeScatteringFunction() const;

  //! Return the Waller-Hartree atomic form factor momentum grid
  const std::vector<double>&
  getWallerHartreeAtomicFormFactorMomentumGrid() const;

  //! Return the Waller-Hartree atomic form factor
  const std::vector<double>& getWallerHartreeAtomicFormFactor() const;

  //! Return the photon energy grid
  const std::vector<double>& getPhotonEnergyGrid() const;

  //! Return the average heating numbers
  const std::vector<double>& getAveragePhotonHeatingNumbers() const;

  //! Return the Waller-Hartree (WH) incoherent photon cross section
  const std::vector<double>&
  getWallerHartreeIncoherentCrossSection() const;

  //! Return the WH incoherent photon cross section threshold energy bin index
  unsigned
  getWallerHartreeIncoherentCrossSectionThresholdEnergyIndex() const;

  //! Return the impluse approx. (IA) incoherent photon cross section
  const std::vector<double>&
  getImpulseApproxIncoherentCrossSection() const;

  //! Return the IA incoherent photon cross section threshold energy bin index
  unsigned
  getImpulseApproxIncoherentCrossSectionThresholdEnergyIndex() const;

  //! Return the subshell Impulse approx. incoherent photon cross section
  const std::vector<double>&
  getImpulseApproxSubshellIncoherentCrossSection(
					       const unsigned subshell ) const;

  //! Return the subshell IA incoherent photon cs threshold energy bin index
  unsigned
  getImpulseApproxSubshellIncoherentCrossSectionThresholdEnergyIndex(
					       const unsigned subshell ) const;

  //! Return the Waller-Hartree coherent cross section
  const std::vector<double>&
  getWallerHartreeCoherentCrossSection() const;

  //! Return the Waller-Hartree coherent cs threshold energy bin index
  unsigned
  getWallerHartreeCoherentCrossSectionThresholdEnergyIndex() const;

  //! Return the pair production cross section
  const std::vector<double>&
  getPairProductionCrossSection() const;

  //! Return the pair production cross section threshold energy bin index
  unsigned getPairProductionCrossSectionThresholdEnergyIndex() const;

  //! Return the Photoelectric effect cross section
  const std::vector<double>& getPhotoelectricCrossSection() const;

  //! Return the Photoelectric effect cross section theshold energy bin index
  unsigned getPhotoelectricCrossSectionThresholdEnergyIndex() const;

  //! Return the Photoelectric effect cross section for a subshell
  const std::vector<double>&
  getSubshellPhotoelectricCrossSection( const unsigned subshell ) const;

  //! Return the subshell Photoelectric effect cross section threshold index
  unsigned
  getSubshellPhotoelectricCrossSectionThresholdEnergyIndex(
					       const unsigned subshell ) const;

  //! Return the Waller-Hartree total cross section
  const std::vector<double>& getWallerHartreeTotalCrossSection() const;

  //! Return the impulse approx. total cross section
  const std::vector<double>& getImpulseApproxTotalCrossSection() const;

//---------------------------------------------------------------------------//
// GET ELECTRON DATA
//---------------------------------------------------------------------------//

  //! Return the upper cutoff scattering angle below which moment preserving elastic scattering is used
  double getCutoffAngle() const;

  //! Return the elastic angular energy grid
  const std::vector<double>& getElasticAngularEnergyGrid() const;

  //! Return the analog elastic scattering angles for an incoming energy
  const std::vector<double>& getAnalogElasticAngles(
					       const double incoming_energy ) const;

  //! Return the analog elastic scatering pdf for an incoming energy
  const std::vector<double>& getAnalogElasticPDF(
					       const double incoming_energy ) const;

  //! Return the screened Rutherford elastic normalization constants
  const std::vector<double>& getScreenedRutherfordNormalizationConstant() const;

  //! Return Moliere's screening constant
  const std::vector<double>& getMoliereScreeningConstant() const;

  //! Return the moment preserving elastic discrete angles for an incoming energy
  const std::vector<double>& getMomentPreservingElasticDiscreteAngles(
					       const double incoming_energy ) const;

  //! Return the moment preserving elastic weights for an incoming energy
  const std::vector<double>& getMomentPreservingElasticWeights(
					       const double incoming_energy ) const;

  //! Return the electroionization energy grid for the recoil electron spectrum for a subshell
  const std::vector<double>& getElectroionizationEnergyGrid(
                           const unsigned subshell ) const;

  //! Return the electroionization recoil energy for a subshell and incoming energy
  const std::vector<double>& getElectroionizationRecoilEnergy(
                           const unsigned subshell,
					       const double incoming_energy ) const;

  //! Return the electroionization recoil energy pdf for a subshell and incoming energy
  const std::vector<double>& getElectroionizationRecoilPDF(
                           const unsigned subshell,
					       const double incoming_energy ) const;

  //! Return the bremsstrahlung energy grid for the secondary photon spectrum
  const std::vector<double>& getBremsstrahlungEnergyGrid() const;

  //! Return the bremsstrahlung photon energy for an incoming energy
  const std::vector<double>& getBremsstrahlungPhotonEnergy(
					       const double incoming_energy ) const;

  //! Return the bremsstrahlung photon energy pdf for an incoming energy
  const std::vector<double>& getBremsstrahlungPhotonPDF(
					       const double incoming_energy ) const;

  //! Return the atomic excitation average energy loss energy grid
  const std::vector<double>& getAtomicExcitationEnergyGrid() const;

  //! Return the atomic excitation average energy loss
  const std::vector<double>& getAtomicExcitationEnergyLoss() const;

  //! Return the electron energy grid
  const std::vector<double>& getElectronEnergyGrid() const;

  //! Return the elastic electron cross section below mu = 0.999999
  const std::vector<double>& getCutoffElasticCrossSection() const;

  //! Return the cutoff elastic cross section threshold energy bin index
  unsigned getCutoffElasticCrossSectionThresholdEnergyIndex() const;

  //! Return the screened Rutherford elastic electron cross section
  const std::vector<double>& getScreenedRutherfordElasticCrossSection() const;

  //! Return the screened Rutherford elastic cross section threshold energy bin index
  unsigned getScreenedRutherfordElasticCrossSectionThresholdEnergyIndex() const;

  //! Return the total elastic electron cross section
  const std::vector<double>& getTotalElasticCrossSection() const;

  //! Return the total elastic cross section threshold energy bin index
  unsigned getTotalElasticCrossSectionThresholdEnergyIndex() const;

  //! Return the Moment Preserving (MP) elastic electron cross section
  const std::vector<double>& getMomentPreservingCrossSection() const;

  //! Return the MP elastic cross section threshold energy bin index
  unsigned getMomentPreservingCrossSectionThresholdEnergyIndex() const;

  //! Return the electroionization electron cross section for a subshell
  const std::vector<double>&
    getElectroionizationCrossSection( const unsigned subshell ) const;

  //! Return the electroionization cross section threshold energy bin index for a subshell
  unsigned getElectroionizationCrossSectionThresholdEnergyIndex(
    const unsigned subshell ) const;

  //! Return the bremsstrahlung electron cross section
  const std::vector<double>& getBremsstrahlungCrossSection() const;

  //! Return the bremsstrahlung cross section threshold energy bin index
  unsigned getBremsstrahlungCrossSectionThresholdEnergyIndex() const;

  //! Return the atomic excitation electron cross section
  const std::vector<double>& getAtomicExcitationCrossSection() const;

  //! Return the atomic excitation cross section threshold energy bin index
  unsigned getAtomicExcitationCrossSectionThresholdEnergyIndex() const;

protected:

  //! Default constructor
  ElectronPhotonRelaxationDataContainer()
  { /* ... */ }

//---------------------------------------------------------------------------//
// SET RELAXATION DATA
//---------------------------------------------------------------------------//

  //! Set the atomic number
  void setAtomicNumber( const unsigned atomic_number );

  //! Set the atomic subshells
  void setSubshells( const std::set<unsigned>& subshells );

  //! Set the occupancy for a subshell
  void setSubshellOccupancy( const unsigned subshell,
			     const double occupancy );

  //! Set the binding energy for a subshell
  void setSubshellBindingEnergy( const unsigned subshell,
				 const double binding_energy );

  //! Set the number of transitions that can fill a subshell vacancy
  void setSubshellRelaxationTransitions( const unsigned subshell,
					 const unsigned transitions );

  //! Set the relaxation vacancies for a subshell
  void setSubshellRelaxationVacancies(
      const unsigned subshell,
      const std::vector<std::pair<unsigned,unsigned> >& relaxation_vacancies );

  //! Set the relaxation particle energies for a subshell
  void setSubshellRelaxationParticleEnergies(
		     const unsigned subshell,
		     const std::vector<double>& relaxation_particle_energies );

  //! Set the relaxation probabilities for a subshell
  void setSubshellRelaxationProbabilities(
			 const unsigned subshell,
			 const std::vector<double>& relaxation_probabilities );

//---------------------------------------------------------------------------//
// SET PHOTON DATA
//---------------------------------------------------------------------------//

  //! Set the Compton profile momentum grid
  void setComptonProfileMomentumGrid(
		    const unsigned subshell,
		    const std::vector<double>& compton_profile_momentum_grid );

  //! Set the Compton profile for a subshell
  void setComptonProfile( const unsigned subshell,
			  const std::vector<double>& compton_profile );

  //! Set the occupation number momentum grid
  void setOccupationNumberMomentumGrid(
		   const unsigned subshell,
		   const std::vector<double>& occupation_number_momentum_grid );

  //! Set the occupation number for a subshell
  void setOccupationNumber( const unsigned subshell,
			   const std::vector<double>& occupation_number );

  //! Set the Waller-Hartree scattering function momentum grid
  void setWallerHartreeScatteringFunctionMomentumGrid(
				    const std::vector<double>& momentum_grid );

  //! Set the Waller-Hartree scattering function
  void setWallerHartreeScatteringFunction(
			      const std::vector<double>& scattering_function );

  //! Set the Waller-Hartree atomic form factor momentum grid
  void setWallerHartreeAtomicFormFactorMomentumGrid(
				    const std::vector<double>& momentum_grid );

  //! Set the Waller-Hartree atomic form factor
  void setWallerHartreeAtomicFormFactor(
			       const std::vector<double>& atomic_form_factor );

  //! Set the photon energy grid
  void setPhotonEnergyGrid( const std::vector<double>& energy_grid );

  //! Set the average photon heating numbers
  void setAveragePhotonHeatingNumbers(
				  const std::vector<double>& heating_numbers );

  //! Set the incoherent photon cross section using Waller-Hartree (WH) theory
  void setWallerHartreeIncoherentCrossSection(
			 const std::vector<double>& incoherent_cross_section );

  //! Set the WH incoherent cross section threshold energy bin index
  void setWallerHartreeIncoherentCrossSectionThresholdEnergyIndex(
						        const unsigned index );

  //! Set the incoherent photon cross section using the impulse approx. (IA)
  void setImpulseApproxIncoherentCrossSection(
			 const std::vector<double>& incoherent_cross_section );

  //! Set the IA incoherent photon cross section threshold energy bin index
  void setImpulseApproxIncoherentCrossSectionThresholdEnergyIndex(
							const unsigned index );

  //! Set the IA subshell incoherent photon cross section
  void setImpulseApproxSubshellIncoherentCrossSection(
			 const unsigned subshell,
			 const std::vector<double>& incoherent_cross_section );

  //! Set the IA subshell incoherent photon cs threshold energy bin index
  void setImpulseApproxSubshellIncoherentCrossSectionThresholdEnergyIndex(
						       const unsigned subshell,
						       const unsigned index );

  //! Set the WH coherent cross section
  void setWallerHartreeCoherentCrossSection(
			   const std::vector<double>& coherent_cross_section );

  //! Set the WH coherent cross section threshold energy bin index
  void setWallerHartreeCoherentCrossSectionThresholdEnergyIndex(
							const unsigned index );

  //! Set the pair production cross section
  void setPairProductionCrossSection(
		    const std::vector<double>& pair_production_cross_section );

  //! Set the pair production cross section threshold energy bin index
  void setPairProductionCrossSectionThresholdEnergyIndex(
							const unsigned index );

  //! Set the Photoelectric effect cross section
  void setPhotoelectricCrossSection(
		      const std::vector<double>& photoelectric_cross_section );

  //! Set the Photoelectric effect cross section threshold energy bin index
  void setPhotoelectricCrossSectionThresholdEnergyIndex(const unsigned index );

  //! Set the Photoelectric effect cross section for a subshell
  void setSubshellPhotoelectricCrossSection(
		      const unsigned subshell,
		      const std::vector<double>& photoelectric_cross_section );

  //! Set the subshell Photoelectric effect cross section threshold index
  void setSubshellPhotoelectricCrossSectionThresholdEnergyIndex(
						       const unsigned subshell,
						       const unsigned index );

  //! Set the Waller-Hartree total cross section
  void setWallerHartreeTotalCrossSection(
			      const std::vector<double>& total_cross_section );

  //! Set the impulse approx. total cross section
  void setImpulseApproxTotalCrossSection(
			      const std::vector<double>& total_cross_section );

//---------------------------------------------------------------------------//
// SET ELECTRON DATA
//---------------------------------------------------------------------------//

  //! Set the elastic cutoff angle
  void setCutoffAngle( const double cutoff_angle );

  //! Set the elastic angular energy grid
  void setElasticAngularEnergyGrid(
    const std::vector<double>& angular_energy_grid );

  //! Set the elastic scattering angles for an incoming energy
  void setAnalogElasticAnglesAtEnergy(
    const double incoming_energy,
    const std::vector<double>& elastic_angles );

  //! Set the elastic scattering pdf for an incoming energy
  void setAnalogElasticPDFAtEnergy(
    const double incoming_energy,
    const std::vector<double>& elastic_pdf );

  //! Set the elastic scattering angles
  void setAnalogElasticAngles(
    const std::map<double,std::vector<double> >& elastic_angles );

  //! Set the elastic scattering pdf
  void setAnalogElasticPDF(
    const std::map<double,std::vector<double> >& elastic_pdf );

  //! Set the screened Rutherford elastic normalization constant
  void setScreenedRutherfordNormalizationConstant(
    const std::vector<double>& screened_rutherford_normalization_constant );

  //! Set Moliere's screening constant
  void setMoliereScreeningConstant(
    const std::vector<double>& moliere_screening_constant );

  //! Set the moment preserving elastic discrete angles for an incoming energy
  void setMomentPreservingElasticDiscreteAngles(
	const double incoming_energy,
	const std::vector<double>& moment_preserving_elastic_discrete_angles );

  //! Set the moment preserving elastic weights for an incoming energy
  void setMomentPreservingElasticWeights(
	const double incoming_energy,
	const std::vector<double>& moment_preserving_elastic_weights );

  //! Set the electroionization energy grid for the recoil electron spectrum
  void setElectroionizationEnergyGrid(
    const unsigned subshell,
    const std::vector<double>& electroionization_energy_grid );

  //! Set the electroionization recoil energy for an incoming energy and subshell
  void setElectroionizationRecoilEnergyAtIncomingEnergy(
    const unsigned subshell,
    const double incoming_energy,
    const std::vector<double>& electroionization_recoil_energy );

  //! Set the electroionization recoil energy pdf for an incoming energy and subshell
  void setElectroionizationRecoilPDFAtIncomingEnergy(
    const unsigned subshell,
    const double incoming_energy,
    const std::vector<double>& electroionization_recoil_pdf );

  //! Set electroionization recoil energy for all incoming energies in a subshell
  void setElectroionizationRecoilEnergy(
    const unsigned subshell,
    const std::map<double,std::vector<double> >& electroionization_recoil_energy );

  //! Set electroionization recoil energy pdf for all incoming energies in a subshell
  void setElectroionizationRecoilPDF(
    const unsigned subshell,
    const std::map<double,std::vector<double> >& electroionization_recoil_pdf );

  //! Set the bremsstrahlung energy grid for the secondary photon spectrum
  void setBremsstrahlungEnergyGrid(
    const std::vector<double>& bremsstrahlung_energy_grid );

  //! Set the bremsstrahlung photon energy for an incoming energy
  void setBremsstrahlungPhotonEnergyAtIncomingEnergy(
    const double incoming_energy,
    const std::vector<double>& bremsstrahlung_photon_energy );

  //! Set the bremsstrahlung photon energy pdf for an incoming energy
  void setBremsstrahlungPhotonPDFAtIncomingEnergy(
    const double incoming_energy,
    const std::vector<double>&  bremsstrahlung_photon_pdf );

  //! Set all the bremsstrahlung photon energy data
  void setBremsstrahlungPhotonEnergy(
    const std::map<double,std::vector<double> >& bremsstrahlung_photon_energy );

  //! Set all the bremsstrahlung photon energy pdf data
  void setBremsstrahlungPhotonPDF(
    const std::map<double,std::vector<double> >&  bremsstrahlung_photon_pdf );

  //! Set the atomic excitation average energy loss energy grid
  void setAtomicExcitationEnergyGrid(
    const std::vector<double>& atomic_excitation_energy_grid );

  //! Set the atomic excitation average energy loss
  void setAtomicExcitationEnergyLoss(
            const std::vector<double>& atomic_excitation_energy_loss );

  //! Set the electron energy grid
  void setElectronEnergyGrid( const std::vector<double>& energy_grid );

  //! Set the elastic electron cross section below mu = 0.999999
  void setCutoffElasticCrossSection(
    const std::vector<double>& cutoff_elastic_cross_section );

  //! Set the elastic cutoff cross section threshold energy bin index
  void setCutoffElasticCrossSectionThresholdEnergyIndex( const unsigned index );

  //! Set the screened Rutherford elastic electron cross section
  void setScreenedRutherfordElasticCrossSection(
    const std::vector<double>& total_elastic_cross_section );

  //! Set the screened Rutherford elastic cross section threshold energy bin index
  void setScreenedRutherfordElasticCrossSectionThresholdEnergyIndex( const unsigned index );

  //! Set the total elastic electron cross section
  void setTotalElasticCrossSection(
    const std::vector<double>& total_elastic_cross_section );

  //! Set the total elastic cross section threshold energy bin index
  void setTotalElasticCrossSectionThresholdEnergyIndex( const unsigned index );

  //! Set the moment preserving elastic electron cross section using Moment Preserving (MP) theory
  void setMomentPreservingCrossSection(
			 const std::vector<double>& moment_preserving_elastic_cross_section );

  //! Set the MP moment preserving elastic cross section threshold energy bin index
  void setMomentPreservingCrossSectionThresholdEnergyIndex(
						        const unsigned index );

  //! Set the electroionization electron cross section for a subshell
  void setElectroionizationCrossSection( const unsigned subshell,
			 const std::vector<double>& electroionization_cross_section );

  //! Set the electroionization cross section threshold energy bin index
  void setElectroionizationCrossSectionThresholdEnergyIndex(
             const unsigned subshell,
             const unsigned index );

  //! Set the bremsstrahlung electron cross section
  void setBremsstrahlungCrossSection(
			 const std::vector<double>& bremsstrahlung_cross_section );

  //! Set the bremsstrahlung cross section threshold energy bin index
  void setBremsstrahlungCrossSectionThresholdEnergyIndex(
                                const unsigned index );

  //! Set the atomic excitation electron cross section
  void setAtomicExcitationCrossSection(
			 const std::vector<double>& atomic_excitation_cross_section );

  //! Set the bremsstrahlung cross section threshold energy bin index
  void setAtomicExcitationCrossSectionThresholdEnergyIndex(
                                const unsigned index );

private:

  // Test if a value is less than or equal to zero
  static bool isValueLessThanOrEqualToZero( const double value );

  // Test if a value is less than zero
  static bool isValueLessThanZero( const double value );

  // Test if a value is greater than one
  static bool isValueGreaterThanOne( const double value );

  // Test if a value is less than minus one
  static bool isValueLessThanMinusOne( const double value );

  // Save the data to an archive
  template<typename Archive>
  void save( Archive& ar, const unsigned version ) const;

  // Load the data from an archive
  template<typename Archive>
  void load( Archive& ar, const unsigned version );

  BOOST_SERIALIZATION_SPLIT_MEMBER();

  // Declare the boost serialization access object as a friend
  friend class boost::serialization::access;

//---------------------------------------------------------------------------//
// RELAXATION DATA
//---------------------------------------------------------------------------//

  // The atomic number
  unsigned d_atomic_number;

  // The atomic subshells (ENDF designators)
  std::set<unsigned> d_subshells;

  // The subshell occupancies
  std::map<unsigned,double> d_subshell_occupancies;

  // The subshell binding energies
  std::map<unsigned,double> d_subshell_binding_energies;

  // The subshell relaxation transitions
  std::map<unsigned,unsigned> d_relaxation_transitions;

  // The subshell relaxation vacancies
  std::map<unsigned,std::vector<std::pair<unsigned,unsigned> > >
  d_relaxation_vacancies;

  // The subshell relaxation particle energies
  std::map<unsigned,std::vector<double> > d_relaxation_particle_energies;

  // The subshell relaxation probabilities
  std::map<unsigned,std::vector<double> > d_relaxation_probabilities;

//---------------------------------------------------------------------------//
// PHOTON DATA
//---------------------------------------------------------------------------//

  // The Compton profile momentum grids (me*c units)
  std::map<unsigned,std::vector<double> > d_compton_profile_momentum_grids;

  // The subshell Compton profiles ((me*c)^-1 units)
  std::map<unsigned,std::vector<double> > d_compton_profiles;

  // The occupation number momentum grids
  std::map<unsigned,std::vector<double> > d_occupation_number_momentum_grids;

  // The subshell occupation numbers
  std::map<unsigned,std::vector<double> > d_occupation_numbers;

  // The Waller-Hartree scattering function momentum grid (1/cm)
  std::vector<double> d_waller_hartree_scattering_function_momentum_grid;

  // The Waller-Hartree scattering function
  std::vector<double> d_waller_hartree_scattering_function;

  // The Waller-Hartree atomic form factor momentum grid (1/cm)
  std::vector<double> d_waller_hartree_atomic_form_factor_momentum_grid;

  // The Waller-Hartree atomic form factor
  std::vector<double> d_waller_hartree_atomic_form_factor;

  // The photon energy grid (MeV)
  std::vector<double> d_photon_energy_grid;

  // The average heating numbers
  std::vector<double> d_average_photon_heating_numbers;

  // The Waller-Hartree incoherent photon cross section (b)
  std::vector<double> d_waller_hartree_incoherent_cross_section;

  // The Waller-Hartree incoherent photon cross section threshold energy index
  unsigned d_waller_hartree_incoherent_cross_section_threshold_index;

  // The impulse approx. incoherent photon cross section (b)
  std::vector<double> d_impulse_approx_incoherent_cross_section;

  // The impulse approx. incoherent photon cross section threshold energy index
  unsigned d_impulse_approx_incoherent_cross_section_threshold_index;

  // The impulse approx. subshell incoherent photon cross sections (b)
  std::map<unsigned,std::vector<double> >
  d_impulse_approx_subshell_incoherent_cross_sections;

  // The impulse approx. subshell incoherent photon cross section thes. indices
  std::map<unsigned,unsigned>
  d_impulse_approx_subshell_incoherent_cross_section_theshold_indices;

  // The Waller-Hartree coherent cross section (b)
  std::vector<double> d_waller_hartree_coherent_cross_section;

  // The Waller-Hartree coherent cross section threshold energy index
  unsigned d_waller_hartree_coherent_cross_section_threshold_index;

  // The pair production cross section (b)
  std::vector<double> d_pair_production_cross_section;

  // The pair production cross section threshold energy index
  unsigned d_pair_production_cross_section_threshold_index;

  // The photoelectric effect cross section (b)
  std::vector<double> d_photoelectric_cross_section;

  // The photoelectric effect cross section energy index
  unsigned d_photoelectric_cross_section_threshold_index;

  // The subshell photoelectric effect cross sections (b)
  std::map<unsigned,std::vector<double> >
  d_subshell_photoelectric_cross_sections;

  // The subshell photoelectric effect cross section threshold indices
  std::map<unsigned,unsigned>
  d_subshell_photoelectric_cross_section_threshold_indices;

  // The Waller-Hartree total cross section (b)
  std::vector<double> d_waller_hartree_total_cross_section;

  // The impulse approx. total cross section (b)
  std::vector<double> d_impulse_approx_total_cross_section;

//---------------------------------------------------------------------------//
// ELECTRON DATA
//---------------------------------------------------------------------------//

  // The elastic cutoff angle
  double d_cutoff_angle;

  // The elastic angular energy grid (MeV)
  std::vector<double> d_angular_energy_grid;

  // The analog elastic scattering angles
  std::map<double,std::vector<double> > d_analog_elastic_angles;

  // The analog elastic scattering pdf
  std::map<double,std::vector<double> > d_analog_elastic_pdf;

  // The screened rutherford normalization constant for elastic scattering
  std::vector<double> d_screened_rutherford_normalization_constant;

  // Moliere's screening constant
  std::vector<double> d_moliere_screening_constant;

  // The moment preserving elastic discrete angles
  std::map<double,std::vector<double> > d_moment_preserving_elastic_discrete_angles;

  // The moment preserving elastic weights
  std::map<double,std::vector<double> > d_moment_preserving_elastic_weights;

  // The electroionization energy grid (MeV) for a subshell
  std::map<unsigned,std::vector<double> > d_electroionization_energy_grid;

  // The electroionization recoil energy for subshell and incoming energy
  std::map<unsigned,std::map<double,std::vector<double> > >
    d_electroionization_recoil_energy;

  // The electroionization recoil pdf for subshell and incoming energy
  std::map<unsigned,std::map<double,std::vector<double> > >
    d_electroionization_recoil_pdf;

  // The bremsstrahlung energy grid (MeV)
  std::vector<double> d_bremsstrahlung_energy_grid;

  // The bremsstrahlung photon energy
  std::map<double,std::vector<double> > d_bremsstrahlung_photon_energy;

  // The bremsstrahlung photon pdf
  std::map<double,std::vector<double> > d_bremsstrahlung_photon_pdf;

  // The atomic excitation energy grid (MeV)
  std::vector<double> d_atomic_excitation_energy_grid;

  // The atomic excitation energy loss
  std::vector<double> d_atomic_excitation_energy_loss;

  // The electron energy grid (MeV)
  std::vector<double> d_electron_energy_grid;

  // The cutoff elastic electron cross section (b)
  std::vector<double> d_cutoff_elastic_cross_section;

  // The cutoff elastic electron cross section threshold energy index
  unsigned d_cutoff_elastic_cross_section_threshold_index;

  // The screened rutherford elastic electron cross section (b)
  std::vector<double> d_screened_rutherford_elastic_cross_section;

  // The screened rutherford elastic electron cross section threshold energy index
  unsigned d_screened_rutherford_elastic_cross_section_threshold_index;

  // The total elastic electron cross section (b)
  std::vector<double> d_total_elastic_cross_section;

  // The total elastic electron cross section threshold energy index
  unsigned d_total_elastic_cross_section_threshold_index;

  // The Moment Preserving elastic electron cross section (b)
  std::vector<double> d_moment_preserving_elastic_cross_section;

  // The Moment Preserving elastic electron cross section threshold energy index
  unsigned d_moment_preserving_elastic_cross_section_threshold_index;

  // The electroionization subshell electron cross section (b)
  std::map<unsigned,std::vector<double> >
    d_electroionization_subshell_cross_section;

  // The hard elastic electron cross section threshold energy index
  std::map<unsigned,unsigned>
    d_electroionization_subshell_cross_section_threshold_index;

  // The bremsstrahlung electron cross section (b)
  std::vector<double> d_bremsstrahlung_cross_section;

  // The bremsstrahlung electron cross section threshold energy index
  unsigned d_bremsstrahlung_cross_section_threshold_index;

  // The atomic excitation electron cross section (b)
  std::vector<double> d_atomic_excitation_cross_section;

  // The atomic excitation electron cross section threshold energy index
  unsigned d_atomic_excitation_cross_section_threshold_index;
};

} // end Data namespace

//---------------------------------------------------------------------------//
// Template Includes
//---------------------------------------------------------------------------//

#include "Data_ElectronPhotonRelaxationDataContainer_def.hpp"

//---------------------------------------------------------------------------//

#endif // end DATA_ELECTRON_PHOTON_RELAXATION_DATA_CONTAINER_HPP

//---------------------------------------------------------------------------//
// end Data_ElectronPhotonRelaxationDataContainer.hpp
//---------------------------------------------------------------------------//


/// \file   legal_entity.hpp
///
/// \brief
///
/// \authors    Maarten P. Scholl
/// \date       2018-04-28
/// \copyright  Copyright 2017-2019 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             Licensed under the Apache License, Version 2.0 (the "License");
///             you may not use this file except in compliance with the License.
///             You may obtain a copy of the License at
///
///                 http://www.apache.org/licenses/LICENSE-2.0
///
///             Unless required by applicable law or agreed to in writing,
///             software distributed under the License is distributed on an "AS
///             IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
///             express or implied. See the License for the specific language
///             governing permissions and limitations under the License.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#ifndef ESL_LEGAL_ENTITY_HPP
#define ESL_LEGAL_ENTITY_HPP

#include <array>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>

#include <boost/serialization/nvp.hpp>
#include <boost/multiprecision/cpp_int.hpp>

#include <cassert>

namespace esl::law {

    struct legal_entity
    {

    };

    ///
    /// \brief  legal_entity implements the Legal Entity Identifier system
    /// described in ISO 17442.
    ///
    struct iso_17442
    {
        ///
        /// \brief The local part describing the local issuing party of the LEI.
        ///
        const std::array<char, 4> local;

        ///
        /// \brief  The code part describing uniquely a firm that is in the
        /// issuing party's business area.
        ///
        const std::array<char, 12> code;

        ///
        /// \brief  Constructs a LEI from the local part and the code part.
        /// \param local    The local part describing the local issuing party of
        /// the LEI. \param code     The code part describing uniquely a firm
        /// that is in the issuing party's business area.
        constexpr iso_17442(const std::array<char, 4> &local = {'0'},
                               const std::array<char, 12> &code = {'0'})
        : local(local), code(code)
        {

        }

        ///
        /// \param text The LEI in string form. Assumes the string contains only
        /// the LEI (20 digits). Tests the reserved
        ///             digits (which are assumed to be '00') and tests the
        ///             checksum (compared to computed value), when assertions
        ///             are enabled.
        explicit iso_17442(const std::string &text)
        : local {text[0], text[1], text[2], text[3]}
        , code {text[6],  text[7],  text[8],  text[9],  text[10], text[11],
                text[12], text[13], text[14], text[15], text[16], text[17]}
        {
            assert(18 == text.length() || 20 == text.length());
            for(auto i = 0; i < 4; ++i) {
                assert('0' <= text[i] && '9' >= text[i]);
            }
            assert('0' == text[4] && '0' == text[5]);
            for(auto i = 6; i < 18; ++i) {
                assert(('0' <= text[i] && '9' >= text[i])
                       || ('A' <= text[i] && 'Z' >= text[i]));
            }

            if(20 == text.length()){
                auto checksum_ = this->checksum();
                assert(text[18] == std::get<0>(checksum_)
                       && text[19] == std::get<1>(checksum_));
            }
        }

        ///
        /// \brief  creates a valid (but fictional) LEI code from a hash. By
        /// default the issuing unit is set to 0000, which
        ///         as of 2019-03 is not used[1], therefore it can be assumed we
        ///         don't collide with existing legal entities. However, there
        ///         can be collisions in the space of 36^12 possible LEI's
        ///
        /// [1] https://www.leiroc.org/faq/index.htm
        /// @return Fictional LEI code based on the given hash.
        ///
        template<typename hashable_t_>
        static iso_17442 create(const hashable_t_ &h,
                                   const std::array<char, 4> local = {'0', '0',
                                                                      '0', '0'})
        {

            constexpr std::array<char, 36> table_ = {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
                'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
                'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

            auto integer_ = std::hash<hashable_t_>()(h);

            std::array<char, 12> code_ = {};

            for(size_t i = 0; i < code_.size(); ++i) {
                code_[i] = table_[integer_ % table_.size()];
                integer_ /= table_.size();
            }

            return iso_17442(local, code_);
        }

        ///
        /// \brief  Computes the two digit checksum which is specified in ISO
        ///         17442. Uses uint128_t in the computation, as
        ///         at least log2(10^4 * 10^2 * 10^(12*2) * 10^2) = 106.301699
        ///         bits are required. For this, Boost.MultiPrecision is
        ///         required (which chooses the platform-appropriate integer
        ///         model).
        ///
        /// \return The checksum digits (not integer values)
        [[nodiscard]] std::tuple<char, char> checksum() const
        {
            boost::multiprecision::uint128_t sum_{0};

            for(auto i = 0; i < 4; ++i) {
                sum_ *= 10;
                sum_ += local[i] - '0';
            }
            sum_ *= 100;
            for(auto i = 0; i < 12; ++i) {
                if('0' <= code[i] && '9' >= code[i]) {
                    sum_ *= 10;
                    sum_ += code[i] - '0';
                } else {
                    sum_ *= 100;
                    sum_ += code[i] - 'A' + 10;
                }
            }
            sum_ *= 100;
            const uint8_t check_ = static_cast<uint8_t>(98 - (sum_ % 97));

            return {static_cast<char>(check_ / 10 + '0'),
                    static_cast<char>(check_ % 10 + '0')};
        }

        ///
        /// \brief  Only the local part and the code part are serialised, as the
        /// reserved digits are always '00' and the
        ///         checksum can be cheaply computed when rendering.
        ///
        /// \tparam archive_t
        /// \param archive
        /// \param version
        template<class archive_t>
        void serialize(archive_t &archive, const unsigned int version)
        {
            (void)version;
            archive &boost::serialization::make_nvp(
                "local", const_cast<std::array<char, 4> &>(local));
            archive &boost::serialization::make_nvp(
                "code", const_cast<std::array<char, 12> &>(code));
        }

        ///
        /// \brief  Renders the LEI: this asserts the reserved digits are '00',
        /// and freshly computes the checksum. \param o \param le \return The
        /// modified output stream
        friend std::ostream &operator << (std::ostream &o, const iso_17442 &le)
        {
            o.write(le.local.data(), le.local.size());
            o << "00";
            o.write(le.code.data(), le.code.size());
            auto checksum_ = le.checksum();
            o << std::get<0>(checksum_) << std::get<1>(checksum_);
            return o;
        }
    };

}  // namespace esl::law

#ifdef WITH_MPI
#include <boost/mpi.hpp>
namespace boost::mpi {
        template<>
    struct is_mpi_datatype<esl::law::legal_entity>
    : mpl::true_
    {

    };

    template<>
    struct is_mpi_datatype<esl::law::iso_17442>
    : mpl::false_
    {

    };
}      // namespace boost::mpi
#endif  // WITH_MPI

#endif  // ESL_LEGAL_ENTITY_HPP


/*
 * drakeUtil.cpp
 *
 *  Created on: Jun 19, 2013
 *      Author: russt
 */

#include "drakeUtil.h"
#include <string.h>
#include <string>
#include <math.h>
#include <limits>
#include <Eigen/Dense>

using namespace std;

bool isa(const mxArray* mxa, const char* class_str)
// mxIsClass seems to not be able to handle derived classes. so i'll implement what I need by calling back to matlab
{
  mxArray* plhs;
  mxArray* prhs[2];
  prhs[0] = const_cast<mxArray*>(mxa);
  prhs[1] = mxCreateString(class_str);
  mexCallMATLAB(1,&plhs,2,prhs,"isa");
  bool tf = *mxGetLogicals(plhs);
  mxDestroyArray(plhs);
  mxDestroyArray(prhs[1]);
  return tf;
}

bool mexCallMATLABsafe(int nlhs, mxArray* plhs[], int nrhs, mxArray* prhs[], const char* filename)
{
  int i;
  mxArray* ex = mexCallMATLABWithTrap(nlhs,plhs,nrhs,prhs,filename);
  if (ex) {
    mexPrintf("DrakeSystem S-Function: error when calling ''%s'' with the following arguments:\n",filename);
    for (i=0; i<nrhs; i++)
      mexCallMATLAB(0,NULL,1,&prhs[i],"disp");
    mxArray *report;
    mexCallMATLAB(1,&report,1,&ex,"getReport");
    char *errmsg = mxArrayToString(report);
    mexPrintf(errmsg);
    mxFree(errmsg);
    mxDestroyArray(report);
    mexErrMsgIdAndTxt("Drake:mexCallMATLABsafe:CallbackError", "Error in MATLAB callback.\nSee additional debugging information above");
    mxDestroyArray(ex);
    return true;
  }
  for (i=0; i<nlhs; i++)
    if (!plhs[i]) {
      mexPrintf("Drake mexCallMATLABsafe: error when calling ''%s'' with the following arguments:\n", filename);
      for (i=0; i<nrhs; i++)
        mexCallMATLAB(0,NULL,1,&prhs[i],"disp");
      mexErrMsgIdAndTxt("Drake:mexCallMATLABsafe:NotEnoughOutputs","Asked for %d outputs, but function only returned %d\n",nrhs,i);
      return true;
    }
  return false;
}

/*
 * @param subclass_name (optional) if you want to call a class that derives from
 * DrakeMexPointer (e.g. so that you can refer to it as something more specific in
 * your matlab code), then you can pass in the alternative name here.  The constructor
 * for this class must take the same inputs as the DrakeMexPointer constructor.
 */
mxArray* createDrakeMexPointer(void* ptr, const char* name, int num_additional_inputs, mxArray* delete_fcn_additional_inputs[], const char* subclass_name)
{
	mxClassID cid;
	if (sizeof(ptr)==4) cid = mxUINT32_CLASS;
	else if (sizeof(ptr)==8) cid = mxUINT64_CLASS;
  else mexErrMsgIdAndTxt("Drake:constructDrakeMexPointer:PointerSize","Are you on a 32-bit machine or 64-bit machine??");

	int nrhs=3+num_additional_inputs;
	mxArray *plhs[1];
  mxArray **prhs;  prhs = new mxArray*[nrhs];

	prhs[0] = mxCreateNumericMatrix(1,1,cid,mxREAL);
  memcpy(mxGetData(prhs[0]),&ptr,sizeof(ptr));

	prhs[1] = mxCreateString(mexFunctionName());

  prhs[2] = mxCreateString(name);

  for (int i=0; i<num_additional_inputs; i++)
    prhs[3+i] = delete_fcn_additional_inputs[i];

//  mexPrintf("deleteMethod = %s\n name =%s\n", deleteMethod,name);

  // call matlab to construct mex pointer object
  if (subclass_name) {
    mexCallMATLABsafe(1,plhs,nrhs,prhs,subclass_name);
    if (!isa(plhs[0],"DrakeMexPointer")) {
      mxDestroyArray(plhs[0]);
      mexErrMsgIdAndTxt("Drake:createDrakeMexPointer:InvalidSubclass","subclass_name is not a valid subclass of DrakeMexPointer");
    }
  }
  else
    mexCallMATLABsafe(1,plhs,nrhs,prhs,"DrakeMexPointer");

  mexLock();

//  mexPrintf("incrementing lock count\n");

  delete[] prhs;
  return plhs[0];
}

void* getDrakeMexPointer(const mxArray* mx)
{
	void* ptr = NULL;

	// todo: optimize this by caching the pointer values, as described in
	// http://groups.csail.mit.edu/locomotion/bugs/show_bug.cgi?id=1590
	mxArray* ptrArray = mxGetProperty(mx,0,"ptr");
	if (!ptrArray)
		mexErrMsgIdAndTxt("Drake:getDrakeMexPointer:BadInputs","cannot retrieve 'ptr' field from this mxArray.  are you sure it's a valid DrakeMexPointer object?");

  if (!mxIsNumeric(ptrArray) || mxGetNumberOfElements(ptrArray)!=1)
    mexErrMsgIdAndTxt("Drake:getDrakeMexPointer:BadInputs","the ptr property of this DrakeMexPointer does not appear to contain a valid pointer");
  memcpy(&ptr,mxGetData(ptrArray),sizeof(ptr));     // note: could use a reinterpret_cast here instead

  return ptr;
}

double angleAverage(double theta1, double theta2) {
  // Computes the average between two angles by averaging points on the unit
  // circle and taking the arctan of the result.
  //   see: http://en.wikipedia.org/wiki/Mean_of_circular_quantities
  // theta1 is a scalar or column vector of angles (rad)
  // theta2 is a scalar or column vector of angles (rad)

  double x_mean = 0.5*(cos(theta1)+cos(theta2));
  double y_mean = 0.5*(sin(theta1)+sin(theta2));

  double angle_mean = atan2(y_mean,x_mean);

  return angle_mean;
}

std::pair<Eigen::Vector3d, double> resolveCenterOfPressure(Eigen::Vector3d torque, Eigen::Vector3d force, Eigen::Vector3d normal, Eigen::Vector3d point_on_contact_plane)
{
  // TODO: implement multi-column version
  using namespace Eigen;

  if (abs(normal.squaredNorm() - 1.0) > 1e-12) {
    mexErrMsgIdAndTxt("Drake:resolveCenterOfPressure:BadInputs", "normal should be a unit vector");
  }

  Vector3d cop;
  double normal_torque_at_cop;

  double fz = normal.dot(force);
  bool cop_exists = abs(fz) > 1e-12;

  if (cop_exists) {
    auto torque_at_point_on_contact_plane = torque - point_on_contact_plane.cross(force);
    double normal_torque_at_point_on_contact_plane = normal.dot(torque_at_point_on_contact_plane);
    auto tangential_torque = torque_at_point_on_contact_plane - normal * normal_torque_at_point_on_contact_plane;
    cop = normal.cross(tangential_torque) / fz + point_on_contact_plane;
    auto torque_at_cop = torque - cop.cross(force);
    normal_torque_at_cop = normal.dot(torque_at_cop);
  }
  else {
    cop.setConstant(std::numeric_limits<double>::quiet_NaN());
    normal_torque_at_cop = std::numeric_limits<double>::quiet_NaN();
  }
  return std::pair<Vector3d, double>(cop, normal_torque_at_cop);
}


#pragma once
#ifndef SMPLX_UTIL_63B0803D_E0C7_4529_A796_9F6ED269E89F
#define SMPLX_UTIL_63B0803D_E0C7_4529_A796_9F6ED269E89F
#include "smplx/defs.hpp"

#include <random>

#define _SMPLX_ASSERT(x)                                                 \
    do {                                                                 \
        if (!(x)) {                                                      \
            std::cerr << "smplx assertion FAILED: \"" << #x << "\" ("    \
                      << (bool)(x) << ")\n  at " << __FILE__ << " line " \
                      << __LINE__ << "\n";                               \
            std::exit(1);                                                \
        }                                                                \
    } while (0)
#define _SMPLX_ASSERT_EQ(x, y)                                            \
    do {                                                                  \
        if ((x) != (y)) {                                                 \
            std::cerr << "smplx assertion FAILED: " << #x << " == " << #y \
                      << " (" << (x) << " != " << (y) << ")\n  at "       \
                      << __FILE__ << " line " << __LINE__ << "\n";        \
            std::exit(1);                                                 \
        }                                                                 \
    } while (0)
#define _SMPLX_ASSERT_NE(x, y)                                            \
    do {                                                                  \
        if ((x) == (y)) {                                                 \
            std::cerr << "smplx assertion FAILED: " << #x << " != " << #y \
                      << " (" << (x) << " == " << (y) << ")\n  at "       \
                      << __FILE__ << " line " << __LINE__ << "\n";        \
            std::exit(1);                                                 \
        }                                                                 \
    } while (0)
#define _SMPLX_ASSERT_LE(x, y)                                                 \
    do {                                                                       \
        if ((x) > (y)) {                                                       \
            std::cerr << "smplx assertion FAILED: " << #x << " <= " << #y      \
                      << " (" << (x) << " > " << (y) << ")\n  at " << __FILE__ \
                      << " line " << __LINE__ << "\n";                         \
            std::exit(1);                                                      \
        }                                                                      \
    } while (0)
#define _SMPLX_ASSERT_LT(x, y)                                           \
    do {                                                                 \
        if ((x) >= (y)) {                                                \
            std::cerr << "smplx assertion FAILED: " << #x << " < " << #y \
                      << " (" << (x) << " >= " << (y) << ")\n  at "      \
                      << __FILE__ << " line " << __LINE__ << "\n";       \
            std::exit(1);                                                \
        }                                                                \
    } while (0)

#include <chrono>
#define _SMPLX_BEGIN_PROFILE \
    auto start = std::chrono::high_resolution_clock::now()
#define _SMPLX_PROFILE(x)                                                      \
    do {                                                                       \
        double _delta = std::chrono::duration<double, std::milli>(             \
                            std::chrono::high_resolution_clock::now() - start) \
                            .count();                                          \
        printf("%s: %f ms = %f fps\n", #x, _delta, 1e3f / _delta);             \
        start = std::chrono::high_resolution_clock::now();                     \
    } while (false)
#define _SMPLX_PROFILE_STEPS(x, stp)                                  \
    do {                                                              \
        printf("%s: %f ms / step\n", #x,                              \
               std::chrono::duration<double, std::milli>(             \
                   std::chrono::high_resolution_clock::now() - start) \
                       .count() /                                     \
                   (stp));                                            \
        start = std::chrono::high_resolution_clock::now();            \
    } while (false)

#include <Eigen/Geometry>

namespace smplx {
namespace util {

const char* gender_to_str(Gender gender);
Gender parse_gender(std::string str);  // Copy intended

// Angle-axis to rotation matrix using custom implementation
template <class T, int Option = Eigen::ColMajor>
inline Eigen::Matrix<T, 3, 3, Option> rodrigues(
    const Eigen::Ref<const Eigen::Matrix<T, 3, 1>>& vec) {
    const T theta = vec.norm();
    const Eigen::Matrix<T, 3, 3, Option> eye =
        Eigen::Matrix<T, 3, 3, Option>::Identity();

    if (std::fabs(theta) < 1e-5f)
        return eye;
    else {
        const T c = std::cos(theta);
        const T s = std::sin(theta);
        const Eigen::Matrix<T, 3, 1> r = vec / theta;
        Eigen::Matrix<T, 3, 3, Option> skew;
        skew << 0, -r.z(), r.y(), r.z(), 0, -r.x(), -r.y(), r.x(), 0;
        return c * eye + (1 - c) * r * r.transpose() + s * skew;
    }
}

// Angle-axis to rotation matrix through Eigen quaternion
// (slightly slower than rodrigues, not useful)
template <class T, int Option = Eigen::ColMajor>
inline Eigen::Matrix<T, 3, 3, Option> rodrigues_eigen(
    const Eigen::Ref<const Eigen::Matrix<T, 3, 1>>& vec) {
    return Eigen::template AngleAxis<T>(vec.norm(), vec / vec.norm())
        .toRotationMatrix();
}

// Affine transformation matrix (hopefully) faster multiplication
// bottom row omitted
template <class T, int Option = Eigen::ColMajor>
inline void mul_affine(
    const Eigen::Ref<const Eigen::Matrix<T, 3, 4, Option>>& a,
    Eigen::Ref<Eigen::Matrix<T, 3, 4, Option>> b) {
    b.template leftCols<3>() =
        a.template leftCols<3>() * b.template leftCols<3>();
    b.template rightCols<1>() =
        a.template rightCols<1>() +
        a.template leftCols<3>() * b.template rightCols<1>();
}

// Affine transformation matrix 'in-place' inverse
template <class T, int Option = Eigen::ColMajor>
inline void inv_affine(Eigen::Ref<Eigen::Matrix<T, 3, 4, Option>> a) {
    a.template leftCols<3>() = a.template leftCols<3>().inverse();
    a.template rightCols<1>() =
        -a.template leftCols<3>() * a.template rightCols<1>();
}

// Homogeneous transformation matrix in-place inverse
template <class T, int Option = Eigen::ColMajor>
inline void inv_homogeneous(Eigen::Ref<Eigen::Matrix<T, 3, 4, Option>> a) {
    a.template leftCols<3>().transposeInPlace();
    a.template rightCols<1>() =
        -a.template leftCols<3>() * a.template rightCols<1>();
}

// Path resolve helper: return a valid path to file in data/
std::string find_data_file(const std::string& data_path);

// Create color from integer
Eigen::Vector3f auto_color(size_t color_index);

// Create table of num_colors colors, shape (num_colors, 3) row-major
Points auto_color_table(size_t num_colors);

// Set matrix to iid multivariate normal
template <class Mat>
void set_randn(Mat& m, float mean = 0.0f, float variance = 1.0f) {
    thread_local std::mt19937 rg{std::random_device{}()};
    std::normal_distribution<float> dist(mean, variance);
    for (size_t i = 0; i < m.rows(); ++i) {
        for (size_t j = 0; j < m.cols(); ++j) {
            m(i, j) = dist(rg);
        }
    }
}

}  // namespace util
}  // namespace smplx

#endif  // ifndef SMPLX_UTIL_63B0803D_E0C7_4529_A796_9F6ED269E89F