//=============================================================================================================
/**
* @file     hpifit.cpp
* @author   Lorenz Esch <Lorenz.Esch@tu-ilmenau.de>;
*           Matti Hamalainen <msh@nmr.mgh.harvard.edu>
* @version  1.0
* @date     March, 2017
*
* @section  LICENSE
*
* Copyright (C) 2017, Lorenz Esch and Matti Hamalainen. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    HPIFit class defintion.
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "hpifit.h"
#include "hpifitdata.h"

#include <fiff/fiff_dig_point_set.h>

#include <utils/ioutils.h>
#include <utils/mnemath.h>

#include <iostream>
#include <fiff/fiff_cov.h>
#include <fstream>

//*************************************************************************************************************
//=============================================================================================================
// Eigen INCLUDES
//=============================================================================================================

#include <Eigen/Dense>

//*************************************************************************************************************
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================

#include <QFuture>
#include <QtConcurrent/QtConcurrent>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace Eigen;
using namespace INVERSELIB;
using namespace FIFFLIB;

//*************************************************************************************************************
//=============================================================================================================
// DEFINE GLOBAL METHODS
//=============================================================================================================

//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

HPIFit::HPIFit()
{

}

//*************************************************************************************************************

void HPIFit::fitHPI(const MatrixXd& t_mat,
                        const Eigen::MatrixXd& t_matProjectors,
                        FiffCoordTrans& transDevHead,
                        const QVector<int>& vFreqs,
                        QVector<double>& vGof,
                        FiffDigPointSet& fittedPointSet,
                        FiffInfo::SPtr pFiffInfo,
                        bool bDoDebug,
                        const QString& sHPIResourceDir)
{
    //Check if data was passed
    if(t_mat.rows() == 0 || t_mat.cols() == 0 ) {
        std::cout<<std::endl<< "HPIFit::fitHPI - No data passed. Returning.";
    }

    //Check if projector was passed
    if(t_matProjectors.rows() == 0 || t_matProjectors.cols() == 0 ) {
        std::cout<<std::endl<< "HPIFit::fitHPI - No projector passed. Returning.";
    }

    vGof.clear();

    struct SensorInfo sensors;
    struct CoilParam coil;
    int numCh = pFiffInfo->nchan;
    int samF = pFiffInfo->sfreq;
    int samLoc = t_mat.cols(); // minimum samples required to localize numLoc times in a second

    //Get HPI coils from digitizers and set number of coils
    int numCoils = 0;
    QList<FiffDigPoint> lHPIPoints;

    for(int i = 0; i < pFiffInfo->dig.size(); ++i) {
        if(pFiffInfo->dig[i].kind == FIFFV_POINT_HPI) {
            numCoils++;
            lHPIPoints.append(pFiffInfo->dig[i]);
        }
    }

    //Set coil frequencies
    Eigen::VectorXd coilfreq(numCoils);

    if(vFreqs.size() >= numCoils) {
        for(int i = 0; i < numCoils; ++i) {
            coilfreq[i] = vFreqs.at(i);
            //std::cout<<std::endl << coilfreq[i] << "Hz";
        }
    } else {
        std::cout<<std::endl<< "HPIFit::fitHPI - Not enough coil frequencies specified. Returning.";
        return;
    }

    // Initialize HPI coils location and moment
    coil.pos = Eigen::MatrixXd::Zero(numCoils,3);
    coil.mom = Eigen::MatrixXd::Zero(numCoils,3);
    coil.dpfiterror = Eigen::VectorXd::Zero(numCoils);
    coil.dpfitnumitr = Eigen::VectorXd::Zero(numCoils);

    // Generate simulated data
    Eigen::MatrixXd simsig(samLoc,numCoils*2);
    Eigen::VectorXd time(samLoc);

    for (int i = 0; i < samLoc; ++i) {
        time[i] = i*1.0/samF;
    }

    for(int i = 0; i < numCoils; ++i) {
        for(int j = 0; j < samLoc; ++j) {
            simsig(j,i) = sin(2*M_PI*coilfreq[i]*time[j]);
            simsig(j,i+numCoils) = cos(2*M_PI*coilfreq[i]*time[j]);
        }
    }

    // Create digitized HPI coil position matrix
    Eigen::MatrixXd headHPI(numCoils,3);

    // check the pFiffInfo->dig information. If dig is empty, set the headHPI is 0;
    if (lHPIPoints.size() > 0) {
        for (int i = 0; i < lHPIPoints.size(); ++i) {
            headHPI(i,0) = lHPIPoints.at(i).r[0];
            headHPI(i,1) = lHPIPoints.at(i).r[1];
            headHPI(i,2) = lHPIPoints.at(i).r[2];
        }
    } else {
        for (int i = 0; i < numCoils; ++i) {
            headHPI(i,0) = 0;
            headHPI(i,1) = 0;
            headHPI(i,2) = 0;
        }
    }

    // Get the indices of inner layer channels and exclude bad channels.
    //TODO: Only supports babymeg and vectorview gradiometeres for hpi fitting.
    QVector<int> innerind(0);

    for (int i = 0; i < numCh; ++i) {
        if(pFiffInfo->chs[i].chpos.coil_type == FIFFV_COIL_BABY_MAG ||
                pFiffInfo->chs[i].chpos.coil_type == FIFFV_COIL_VV_PLANAR_T1 ||
                pFiffInfo->chs[i].chpos.coil_type == FIFFV_COIL_VV_PLANAR_T2 ||
                pFiffInfo->chs[i].chpos.coil_type == FIFFV_COIL_VV_PLANAR_T3) {
            // Check if the sensor is bad, if not append to innerind
            if(!(pFiffInfo->bads.contains(pFiffInfo->ch_names.at(i)))) {
                innerind.append(i);
            }
        }
    }

    //Create new projector based on the excluded channels, first exclude the rows then the columns
    MatrixXd matProjectorsRows(innerind.size(),t_matProjectors.cols());
    MatrixXd matProjectorsInnerind(innerind.size(),innerind.size());

    for (int i = 0; i < matProjectorsRows.rows(); ++i) {
        matProjectorsRows.row(i) = t_matProjectors.row(innerind.at(i));
    }

    for (int i = 0; i < matProjectorsInnerind.cols(); ++i) {
        matProjectorsInnerind.col(i) = matProjectorsRows.col(innerind.at(i));
    }

    //UTILSLIB::IOUtils::write_eigen_matrix(matProjectorsInnerind, "matProjectorsInnerind.txt");
    //UTILSLIB::IOUtils::write_eigen_matrix(t_matProjectors, "t_matProjectors.txt");

    // Initialize inner layer sensors
    sensors.coilpos = Eigen::MatrixXd::Zero(innerind.size(),3);
    sensors.coilori = Eigen::MatrixXd::Zero(innerind.size(),3);
    sensors.tra = Eigen::MatrixXd::Identity(innerind.size(),innerind.size());

    for(int i = 0; i < innerind.size(); i++) {
        sensors.coilpos(i,0) = pFiffInfo->chs[innerind.at(i)].chpos.r0[0];
        sensors.coilpos(i,1) = pFiffInfo->chs[innerind.at(i)].chpos.r0[1];
        sensors.coilpos(i,2) = pFiffInfo->chs[innerind.at(i)].chpos.r0[2];
        sensors.coilori(i,0) = pFiffInfo->chs[innerind.at(i)].chpos.ez[0];
        sensors.coilori(i,1) = pFiffInfo->chs[innerind.at(i)].chpos.ez[1];
        sensors.coilori(i,2) = pFiffInfo->chs[innerind.at(i)].chpos.ez[2];
    }

    Eigen::MatrixXd topo(innerind.size(), numCoils*2);
    Eigen::MatrixXd amp(innerind.size(), numCoils);
    Eigen::MatrixXd ampC(innerind.size(), numCoils);

    // Get the data from inner layer channels
    Eigen::MatrixXd innerdata(innerind.size(), t_mat.cols());

    for(int j = 0; j < innerind.size(); ++j) {
        innerdata.row(j) << t_mat.row(innerind[j]);
    }

    // Calculate topo
    topo = innerdata * UTILSLIB::MNEMath::pinv(simsig).transpose(); // topo: # of good inner channel x 8

    // Select sine or cosine component depending on the relative size
    amp  = topo.leftCols(numCoils); // amp: # of good inner channel x 4
    ampC = topo.rightCols(numCoils);

    for(int j = 0; j < numCoils; ++j) {
       float nS = 0.0;
       float nC = 0.0;
       for(int i = 0; i < innerind.size(); ++i) {
           nS += amp(i,j)*amp(i,j);
           nC += ampC(i,j)*ampC(i,j);
       }

       if(nC > nS) {
         for(int i = 0; i < innerind.size(); ++i) {
           amp(i,j) = ampC(i,j);
         }
       }
    }

    //Find good seed point/starting point for the coil position in 3D space
    //Find biggest amplitude per pickup coil (sensor) and store corresponding sensor channel index
    VectorXi chIdcs(numCoils);

    for (int j = 0; j < numCoils; j++) {
        double maxVal = 0;
        int chIdx = 0;

        for (int i = 0; i < amp.rows(); ++i) {
            if(std::fabs(amp(i,j)) > maxVal) {
                maxVal = std::fabs(amp(i,j));

                if(chIdx < innerind.size()) {
                    chIdx = innerind.at(i);
                }
            }
        }

        chIdcs(j) = chIdx;
    }

    //Generate seed point by projection the found channel position 3cm inwards
    Eigen::MatrixXd coilPos = Eigen::MatrixXd::Zero(numCoils,3);

    for (int j = 0; j < chIdcs.rows(); ++j) {
        int chIdx = chIdcs(j);

        if(chIdx < pFiffInfo->chs.size()) {
            double x = pFiffInfo->chs.at(chIdcs(j)).chpos.r0[0];
            double y = pFiffInfo->chs.at(chIdcs(j)).chpos.r0[1];
            double z = pFiffInfo->chs.at(chIdcs(j)).chpos.r0[2];

            coilPos(j,0) = -1 * pFiffInfo->chs.at(chIdcs(j)).chpos.ez[0] * 0.03 + x;
            coilPos(j,1) = -1 * pFiffInfo->chs.at(chIdcs(j)).chpos.ez[1] * 0.03 + y;
            coilPos(j,2) = -1 * pFiffInfo->chs.at(chIdcs(j)).chpos.ez[2] * 0.03 + z;
        }

        //std::cout << "HPIFit::fitHPI - Coil " << j << " max value index " << chIdx << std::endl;
    }

    coil.pos = coilPos;

    coil = dipfit(coil, sensors, amp, numCoils, matProjectorsInnerind);

    Eigen::Matrix4d trans = computeTransformation(headHPI, coil.pos);
    //Eigen::Matrix4d trans = computeTransformation(coil.pos, headHPI);

    // Store the final result to fiff info
    // Set final device/head matrix and its inverse to the fiff info
    transDevHead.from = 1;
    transDevHead.to = 4;

    for(int r = 0; r < 4; ++r) {
        for(int c = 0; c < 4 ; ++c) {
            transDevHead.trans(r,c) = trans(r,c);
        }
    }

    // Also store the inverse
    transDevHead.invtrans = transDevHead.trans.inverse();

    //Calculate GOF
    MatrixXd temp = coil.pos;
    temp.conservativeResize(coil.pos.rows(),coil.pos.cols()+1);

    temp.block(0,3,numCoils,1).setOnes();
    temp.transposeInPlace();

    MatrixXd testPos = trans * temp;
    MatrixXd diffPos = testPos.block(0,0,3,numCoils) - headHPI.transpose();

    for(int i = 0; i < diffPos.cols(); ++i) {
        vGof.append(diffPos.col(i).norm());
    }

    //Generate final fitted points and store in digitizer set
    for(int i = 0; i < coil.pos.rows(); ++i) {
        FiffDigPoint digPoint;
        digPoint.kind = FIFFV_POINT_EEG;
        digPoint.ident = i;
        digPoint.r[0] = coil.pos(i,0);
        digPoint.r[1] = coil.pos(i,1);
        digPoint.r[2] = coil.pos(i,2);

        fittedPointSet << digPoint;
    }

    if(bDoDebug) {
        // DEBUG HPI fitting and write debug results
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - dpfiterror" << coil.dpfiterror << std::endl << coil.pos << std::endl;
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - Initial seed point for HPI coils" << std::endl << coil.pos << std::endl;
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - temp" << std::endl << temp << std::endl;
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - testPos" << std::endl << testPos << std::endl;
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - Diff fitted - original" << std::endl << diffPos << std::endl;
        std::cout << std::endl << std::endl << "HPIFit::fitHPI - dev/head trans" << std::endl << trans << std::endl;

        QString sTimeStamp = QDateTime::currentDateTime().toString("yyMMdd_hhmmss");

        if(!QDir(sHPIResourceDir).exists()) {
            QDir().mkdir(sHPIResourceDir);
        }

        UTILSLIB::IOUtils::write_eigen_matrix(coilPos, QString("%1/%2_coilPosSeed_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        UTILSLIB::IOUtils::write_eigen_matrix(coil.pos, QString("%1/%2_coilPos_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        UTILSLIB::IOUtils::write_eigen_matrix(headHPI, QString("%1/%2_headHPI_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        MatrixXd testPosCut = testPos.transpose();//block(0,0,3,4);
        UTILSLIB::IOUtils::write_eigen_matrix(testPosCut, QString("%1/%2_testPos_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        MatrixXi idx_mat(chIdcs.rows(),1);
        idx_mat.col(0) = chIdcs;
        UTILSLIB::IOUtils::write_eigen_matrix(idx_mat, QString("%1/%2_idx_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        MatrixXd coilFreq_mat(coilfreq.rows(),1);
        coilFreq_mat.col(0) = coilfreq;
        UTILSLIB::IOUtils::write_eigen_matrix(coilFreq_mat, QString("%1/%2_coilFreq_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        UTILSLIB::IOUtils::write_eigen_matrix(diffPos, QString("%1/%2_diffPos_mat").arg(sHPIResourceDir).arg(sTimeStamp));

        UTILSLIB::IOUtils::write_eigen_matrix(amp, QString("%1/%2_amp_mat").arg(sHPIResourceDir).arg(sTimeStamp));
    }
}

//*************************************************************************************************************

CoilParam HPIFit::dipfit(struct CoilParam coil, struct SensorInfo sensors, const Eigen::MatrixXd& data, int numCoils, const Eigen::MatrixXd& t_matProjectors)
{
    //Do this in conncurrent mode
    //Generate QList structure which can be handled by the QConcurrent framework
    QList<HPIFitData> lCoilData;

    for(qint32 i = 0; i < numCoils; ++i) {
        HPIFitData coilData;
        coilData.coilPos = coil.pos.row(i);
        coilData.sensorData = data.col(i);
        coilData.sensorPos = sensors;
        coilData.matProjector = t_matProjectors;

        lCoilData.append(coilData);
    }
    //Do the concurrent filtering
    if(!lCoilData.isEmpty()) {
//        //Do sequential
//        for(int l = 0; l < lCoilData.size(); ++l) {
//            doDipfitConcurrent(lCoilData[l]);
//        }

        //Do concurrent
        QFuture<void> future = QtConcurrent::map(lCoilData,
                                             &HPIFitData::doDipfitConcurrent);
        future.waitForFinished();

        //Transform results to final coil information
        for(qint32 i = 0; i < lCoilData.size(); ++i) {
            coil.pos.row(i) = lCoilData.at(i).coilPos;
            coil.mom = lCoilData.at(i).errorInfo.moment.transpose();
            coil.dpfiterror(i) = lCoilData.at(i).errorInfo.error;
            coil.dpfitnumitr(i) = lCoilData.at(i).errorInfo.numIterations;

            //std::cout<<std::endl<< "HPIFit::dipfit - Itr steps for coil " << i << " =" <<coil.dpfitnumitr(i);
        }
    }

    return coil;
}

//*************************************************************************************************************

Eigen::Matrix4d HPIFit::computeTransformation(Eigen::MatrixXd NH, Eigen::MatrixXd BT)
{
    Eigen::MatrixXd xdiff, ydiff, zdiff, C, Q;
    Eigen::Matrix4d transFinal = Eigen::Matrix4d::Identity(4,4);
    Eigen::Matrix4d Rot = Eigen::Matrix4d::Zero(4,4);
    Eigen::Matrix4d Trans = Eigen::Matrix4d::Identity(4,4);
    double meanx,meany,meanz,normf;

    for(int i = 0; i < 15; ++i) {
        // Calculate mean translation for all points -> centroid of both data sets
        xdiff = NH.col(0) - BT.col(0);
        ydiff = NH.col(1) - BT.col(1);
        zdiff = NH.col(2) - BT.col(2);

        meanx = xdiff.mean();
        meany = ydiff.mean();
        meanz = zdiff.mean();

        // Apply translation -> bring both data sets to the same center location
        for (int j = 0; j < BT.rows(); ++j) {
            BT(j,0) = BT(j,0) + meanx;
            BT(j,1) = BT(j,1) + meany;
            BT(j,2) = BT(j,2) + meanz;
        }

        // Estimate rotation component
        C = BT.transpose() * NH;

        Eigen::JacobiSVD< Eigen::MatrixXd > svd(C ,Eigen::ComputeThinU | Eigen::ComputeThinV);

        Q = svd.matrixU() * svd.matrixV().transpose();

        //Handle special reflection case
        if(Q.determinant() < 0) {
            Q(0,2) = Q(0,2) * -1;
            Q(1,2) = Q(1,2) * -1;
            Q(2,2) = Q(2,2) * -1;
        }

        // Apply rotation on translated points
        BT = BT * Q;

        // Calculate GOF
        normf = (NH.transpose()-BT.transpose()).norm();

        // Store rotation part to transformation matrix
        Rot(3,3) = 1;
        for(int j = 0; j < 3; ++j) {
            for(int k = 0; k < 3; ++k) {
                Rot(j,k) = Q(k,j);
            }
        }

        // Store translation part to transformation matrix
        Trans(0,3) = meanx;
        Trans(1,3) = meany;
        Trans(2,3) = meanz;

        // Safe rotation and translation to final matrix for next iteration step
        // This step is safe to do since we change one of the input point sets (BT)
        // ToDo: Replace this for loop with a least square solution process
        transFinal = Rot * Trans * transFinal;
    }

    return transFinal;
}


/*************** <auto-copyright.rb BEGIN do not edit this line> **************
 *
 * Copyright 2012-2013 by Ames Laboratory
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 *************** <auto-copyright.rb END do not edit this line> ***************/

// --- LEAF Includes --- //
#include <leaf/wrapper/daycent/soil/Soils.h>
#include <leaf/wrapper/daycent/file/F_header.h>

// --- Boost Includes --- //
#include <boost/assign.hpp>

// --- STL Includes --- //
#include <iostream>
#include <fstream>
#include <cmath>

using namespace leaf::open;
using namespace leaf::util;

namespace leaf
{
namespace wrapper
{
namespace daycent
{
namespace soil
{

////////////////////////////////////////////////////////////////////////////////
std::set< unsigned int > const Soils::ADEP = boost::assign::list_of
    ( 10 )                          //ADEP(1)
    ( 30 )                          //ADEP(2)
    ( 45 )                          //ADEP(3)
    ( 60 )                          //ADEP(4)
    ( 90 )                          //ADEP(5)
    ( 120 )                         //ADEP(6)
    ( 150 )                         //ADEP(7)
    ( 180 )                         //ADEP(8)
    ( 210 )                         //ADEP(9)
    ( 240 );                        //ADEP(10)
////////////////////////////////////////////////////////////////////////////////
std::set< unsigned int > const Soils::LAYER = boost::assign::list_of
    ( 2 ) ( 5 ) ( 10 )              //ADEP(1)
    ( 20 ) ( 30 )                   //ADEP(2)
    ( 45 )                          //ADEP(3)
    ( 60 )                          //ADEP(4)
    ( 75 ) ( 90 )                   //ADEP(5)
    ( 105 ) ( 120 )                 //ADEP(6)
    ( 150 )                         //ADEP(7)
    ( 180 )                         //ADEP(8)
    ( 210 )                         //ADEP(9)
    ( 240 );                        //ADEP(10)
////////////////////////////////////////////////////////////////////////////////
std::vector< double > const Soils::EVAP_COEFF = boost::assign::list_of
    ( 0.8 ) ( 0.2 ) ( 0.0 )         //ADEP(1)
    ( 0.0 ) ( 0.0 )                 //ADEP(2)
    ( 0.0 )                         //ADEP(3)
    ( 0.0 )                         //ADEP(4)
    ( 0.0 ) ( 0.0 )                 //ADEP(5)
    ( 0.0 ) ( 0.0 )                 //ADEP(6)
    ( 0.0 )                         //ADEP(7)
    ( 0.0 )                         //ADEP(8)
    ( 0.0 )                         //ADEP(9)
    ( 0.0 );                        //ADEP(10)
////////////////////////////////////////////////////////////////////////////////
std::vector< double > const Soils::TRANS_COEFF = boost::assign::list_of
    ( 0.01 ) ( 0.04 ) ( 0.25 )      //ADEP(1)
    ( 0.30 ) ( 0.10 )               //ADEP(2)
    ( 0.05 )                        //ADEP(3)
    ( 0.04 )                        //ADEP(4)
    ( 0.03 ) ( 0.02 )               //ADEP(5)
    ( 0.01 ) ( 0.0 )                //ADEP(6)
    ( 0.0 )                         //ADEP(7)
    ( 0.0 )                         //ADEP(8)
    ( 0.0 )                         //ADEP(9)
    ( 0.0 );                        //ADEP(10)
////////////////////////////////////////////////////////////////////////////////
std::vector< double > const Soils::MIN_VSWC = boost::assign::list_of
    ( 0.8 ) ( 0.6 ) ( 0.4 )         //ADEP(1)
    ( 0.1 ) ( 0.0 )                 //ADEP(2)
    ( 0.0 )                         //ADEP(3)
    ( 0.0 )                         //ADEP(4)
    ( 0.0 ) ( 0.0 )                 //ADEP(5)
    ( 0.0 ) ( 0.0 )                 //ADEP(6)
    ( 0.0 )                         //ADEP(7)
    ( 0.0 )                         //ADEP(8)
    ( 0.0 )                         //ADEP(9)
    ( 0.0 );                        //ADEP(10)
////////////////////////////////////////////////////////////////////////////////
Soils::Soils()
{
    ;
}
////////////////////////////////////////////////////////////////////////////////
Soils::~Soils()
{
    ;
}
////////////////////////////////////////////////////////////////////////////////
bool Soils::LoadSQL(
    soi::Component const& component,
    std::string const& dir,
    std::string const& abbr )
{
    std::string cokey = Convert< std::string >( component.GetCokey() );

    //Get the component horizons
    soi::CHorizons const& horizons = component.GetHorizons();
    soi::CHorizonsByHzdept::const_iterator chitr =
        horizons.get< HZDEPT >().begin();

    //This requires at least the first horizon to be valid
    if( chitr == horizons.get< HZDEPT >().end() ) return false;
    Layer layer( **chitr );
    if( !( layer.IsValid() ) ) return false;

    Layers layers;
    double totEvapCoeff( 0.0 ), totTransCoeff( 0.0 );
    std::set< unsigned int >::const_iterator itr = LAYER.begin();
    for( unsigned int lyr = 0; itr != LAYER.end(); ++itr, ++lyr )
    {
        LayerPtr nxtlay( NULL );
        unsigned int const& value = *itr;
        double const& hzdept = layer.GetMinDepth();
        double hzdepb = layer.GetMaxDepth();
        bool success( true );
        if( hzdepb < value )
        {
            Layer agglay = layer;
            agglay *= ( hzdepb - hzdept ) / ( value - hzdept );
            do
            {
                success = ( ++chitr != horizons.get< HZDEPT >().end() );
                if( !success ) break;
                if( Convert< double >((**chitr).GetSandtotal()) == 0 &&
                    Convert< double >((**chitr).GetSilttotal()) == 0 &&
                    Convert< double >((**chitr).GetClaytotal()) == 0 )
                {
                    success = false;
                }
                else
                {
                    nxtlay.reset( new Layer( **chitr ) );
                    success = layer.IsValid();
                }

            if( !success ) break;
                double const& maxdept = nxtlay->GetMaxDepth();
                agglay += *nxtlay * ( ( std::min< double >(
                    maxdept, value ) - hzdepb ) / ( value - hzdept ) );
                hzdepb = maxdept;
            }
            while( hzdepb < value );
            if( success ) layer = agglay;
        }

        layer.SetMaxDepth( value );
        layer.SetEvapCoeff( EVAP_COEFF.at( lyr ) );
        layer.SetTransCoeff( TRANS_COEFF.at( lyr ) );
        layer.SetMinVolH2O( MIN_VSWC.at( lyr ) * layer.GetWltPnt() );
        totEvapCoeff += layer.GetEvapCoeff();
        totTransCoeff += layer.GetTransCoeff();
        layers.push_back( layer );

        if( !success ) break;
        if( hzdepb > value )
        {
            if( nxtlay != NULL ) layer = *nxtlay;
            layer.SetMinDepth( value );
            layer.SetMaxDepth( hzdepb );
        }
        else
        {
            if( ++chitr == horizons.get< HZDEPT >().end() ) break;
            layer = Layer( **chitr );
            if( !( layer.IsValid() ) ) break;
        }
    }

    //file: DailyDayCent/Swconst.h - #define FNSOIL "soils.in"
    std::ofstream myfile;
    std::string fileDir( dir );
    myfile.open( ( fileDir.append( "/soils.in" ) ).c_str() );
    //Update site file
    unsigned int maxdepth = layer.GetMaxDepth();
    std::cout << "Max Depth: " << maxdepth << std::endl;
    std::set< unsigned int >::const_iterator adep = ADEP.begin();
    for( ; adep != ADEP.end(); ++adep )
    {
        std::cout << *adep << std::endl;
        if( maxdepth <= *adep )
        {
            maxdepth = *adep;
            std::cout << maxdepth << std::endl;
            break;
        }
    }
    std::cout << maxdepth << std::endl;

    SetNLayer(
        std::distance( ADEP.begin(), ADEP.find( maxdepth ) ) + 1);
    SetSilt( Convert< double >( (**horizons.get< HZDEPT >().begin()).GetSilttotal() ) );
    SetSand( Convert< double >( (**horizons.get< HZDEPT >().begin()).GetSandtotal() ) );
    SetClay( Convert< double >( (**horizons.get< HZDEPT >().begin()).GetClaytotal() ) );
    SetBulkDensity( Convert< double >( (**horizons.get< HZDEPT >().begin()).GetDbthirdbar() ) );
    std::cout << Convert< double >( (**horizons.get< HZDEPT >().begin()).GetDbthirdbar() ) << std::endl;

    std::cout << "NALYER " << std::distance( ADEP.begin(), ADEP.find( maxdepth ) ) + 1 << std::endl;
    //opt.SetValue( "NLAYER", adep );
    //Use first layer to set these values (probably not needed at all)
    Layers::iterator litr = layers.begin();

    //Normalize evapCoeff and transCoeff to 1 and write layer to file
    for( ; litr != layers.end(); ++litr )
    {
        Layer& l = *litr;
        assert( totEvapCoeff != 0.0 && totTransCoeff != 0.0 );
        l.SetEvapCoeff( l.GetEvapCoeff() / totEvapCoeff );
        l.SetTransCoeff( l.GetTransCoeff() / totTransCoeff );
        myfile << l;
    }
    myfile.close();

    return true;
}
////////////////////////////////////////////////////////////////////////////////
Soils::Layer::Layer(
    open::soi::CHorizon const& horizon )
    :
    m_isValid( false ),
    m_minDepth( NaN ),
    m_maxDepth( NaN ),
    m_bulkDens( NaN ),
    m_fldCpcty( NaN ),
    m_wltPnt( NaN ),
    m_evapCoeff( NaN ),
    m_transCoeff( NaN ),
    m_sandFrac( NaN ),
    m_clayFrac( NaN ),
    m_siltFrac( NaN ),
    m_om( NaN ),
    m_minVolH2O( NaN ),
    m_satHydCond( NaN ),
    m_ph( NaN )
{
    m_sandFrac = Convert< double >( horizon.GetSandtotal(), NaN ) / 100.0;
    m_clayFrac = Convert< double >( horizon.GetClaytotal(), NaN ) / 100.0;
    m_siltFrac = Convert< double >( horizon.GetSilttotal(), NaN ) / 100.0;
    double acoef = (exp(-4.396 - 0.0715 * m_clayFrac*100 - 4.88e-4 *
              pow(m_sandFrac*100, 2) - 4.285e-5 * pow(m_sandFrac*100, 2) * m_clayFrac*100));
    double bcoef = (-3.14 - 0.00222 * pow(m_clayFrac*100, 2) - 3.484e-5 *
              pow(m_sandFrac*100, 2) * m_clayFrac*100);
    double sat = (0.332 - 7.251e-4 * m_sandFrac*100 + 0.1276 * log10(m_clayFrac*100));
    std::cout << acoef << " " << bcoef << " " << sat << std::endl;

    double wp_raw = (pow((15.0 / acoef), (1.0 / bcoef)));
    double fc_raw = (pow((0.333 / acoef), (1.0 / bcoef)));
    double ksat_raw = (exp((12.012 - 0.0755 * m_sandFrac*100) + (-3.895 + 0.03671 *
             m_sandFrac*100 - 0.1103 * m_clayFrac*100 + 8.7546e-4 * pow(m_clayFrac*100, 2)) /
             sat));
    double bd_raw = (1 - sat) * 2.65f;
    std::cout << wp_raw << " " << fc_raw << " " << ksat_raw << std::endl;

    /* Corrections from Steve Del Grosso */
    double wiltpt = wp_raw + (-0.15f * wp_raw);
    double fieldc = fc_raw + (0.07f * fc_raw);
    double ksat = ksat_raw / 3600;
    double bulkd = bd_raw + (-0.08f * bd_raw);

    m_minDepth = Convert< double >( horizon.GetHzdept(), NaN );
    m_maxDepth = Convert< double >( horizon.GetHzdepb(), NaN );
    m_bulkDens = bulkd;//bd_raw;//Convert< double >( horizon.GetDbthirdbar(), NaN );
    m_fldCpcty = fieldc;//fc_raw;//Convert< double >( horizon.GetWthirdbar(), NaN ) / 100.0;
    m_wltPnt = wiltpt;//wp_raw;//Convert< double >( horizon.GetWfifteenbar(), NaN ) / 100.0;

    m_om = Convert< double >( horizon.GetOm(), NaN ) / 100.0;
    m_minVolH2O = wiltpt;//wp_rawK;
    // micrometer/sec to cm/sec
    m_satHydCond = ksat;//ksat_raw;//Convert< double >( horizon.GetKsat(), NaN ) / 10000.0;
    m_ph = Convert< double >( horizon.GetPh01mcacl2(),
        Convert< double >( horizon.GetPh1to1h2o(), NaN ) );

    //Normalize to make sure that the SAND, SILT, and CLAY values sum to 1.0
    double sscsum = m_sandFrac + m_siltFrac + m_clayFrac;
    std::cout << m_sandFrac << " " << m_siltFrac << " " << m_clayFrac << " " <<sscsum << std::endl;
    if( sscsum != 1.0 )
    {
        m_sandFrac /= sscsum;
        m_siltFrac /= sscsum;
        m_clayFrac /= sscsum;
    }

    //
    m_isValid = !(
        *this == NaN ||
        m_maxDepth < m_minDepth ||
        fabs( sscsum - 1.0 ) > 0.1 ||
        sscsum == 0 );

    SetIsValid( m_isValid );
}
////////////////////////////////////////////////////////////////////////////////
Soils::Layer::~Layer()
{
    ;
}
////////////////////////////////////////////////////////////////////////////////
bool const& Soils::Layer::IsValid() const
{
    return m_isValid;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetIsValid( bool valid )
{
    m_isValid = valid;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetMinDepth() const
{
    return m_minDepth;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetMinDepth(
    double const& minDepth )
{
    m_minDepth = minDepth;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetMaxDepth() const
{
    return m_maxDepth;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetMaxDepth(
    double const& maxDepth )
{
    m_maxDepth = maxDepth;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetBulkDens() const
{
    return m_bulkDens;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetFldCpcty() const
{
    return m_fldCpcty;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetWltPnt() const
{
    return m_wltPnt;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetEvapCoeff() const
{
    return m_evapCoeff;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetEvapCoeff(
    double const& evapCoeff )
{
    m_evapCoeff = evapCoeff;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetTransCoeff() const
{
    return m_transCoeff;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetTransCoeff(
    double const& transCoeff )
{
    m_transCoeff = transCoeff;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetMinVolH2O() const
{
    return m_minVolH2O;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::Layer::SetMinVolH2O(
    double const& minVolH2O )
{
    m_minVolH2O = minVolH2O;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetSandFrac() const
{
    return m_sandFrac;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetClayFrac() const
{
    return m_clayFrac;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetSiltFrac() const
{
    return m_siltFrac;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetOm() const
{
    return m_om;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetSatHydCond() const
{
    return m_satHydCond;
}
////////////////////////////////////////////////////////////////////////////////
double const& Soils::Layer::GetPh() const
{
    return m_ph;
}
////////////////////////////////////////////////////////////////////////////////
int Soils::GetNLayer()
{
    return m_nlayer;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::SetNLayer( int const& layer )
{
    m_nlayer = layer;
}
////////////////////////////////////////////////////////////////////////////////
double Soils::GetSilt()
{
    return m_silt;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::SetSilt( double const& silt )
{
    m_silt = silt;
}
////////////////////////////////////////////////////////////////////////////////
double Soils::GetSand()
{
    return m_sand;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::SetSand( double const& sand )
{
    m_sand = sand;
}
////////////////////////////////////////////////////////////////////////////////
double Soils::GetClay()
{
    return m_clay;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::SetClay( double const& clay )
{
    m_clay = clay;
}
////////////////////////////////////////////////////////////////////////////////
double Soils::GetBulkDensity()
{
    return m_bulkden;
}
////////////////////////////////////////////////////////////////////////////////
void Soils::SetBulkDensity( double const& bulkden )
{
    m_bulkden = bulkden;
}
////////////////////////////////////////////////////////////////////////////////

} //end soil
} //end daycent
} //end wrapper
} //end leaf


/*
  Author(s):      Matthew Celnik (msc37)
  Project:        sweepc (population balance solver)
  Sourceforge:    http://sourceforge.net/projects/mopssuite

  Copyright (C) 2008 Matthew S Celnik.

  File purpose:
    Implementation of the Primary class declared in the
    swp_primary.h header file.

  Licence:
    This file is part of "sweepc".

    sweepc is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Contact:
    Dr Markus Kraft
    Dept of Chemical Engineering
    University of Cambridge
    New Museums Site
    Pembroke Street
    Cambridge
    CB2 3RA
    UK

    Email:       mk306@cam.ac.uk
    Website:     http://como.cheng.cam.ac.uk
*/

#include "swp_primary.h"
#include "swp_model_factory.h"
#include "swp_cell.h"
#include "swp_kmc_typedef.h"

#include <stdexcept>
#include <memory.h>

#include <boost/random/uniform_smallint.hpp>

using namespace Sweep;
using namespace Sweep::KMC_ARS;

using namespace std;

// CONSTRUCTORS AND DESTRUCTORS.

//! Default constructor (protected).
AggModels::Primary::Primary(void)
: m_pmodel(NULL), m_createt(0.0), m_time(0.0), m_diam(0.0), m_dcol(0.0),
m_dmob(0.0), m_surf(0.0), m_vol(0.0), m_mass(0.0), m_numcarbon(0), m_frag(0), m_numOf6Rings(0), m_phaseterm(0.0)
{
}

// Initialising constructor.
AggModels::Primary::Primary(double time, const Sweep::ParticleModel &model)
{
    init();
    m_pmodel  = &model;

    // Set particle create and update times.
    m_createt = time;
    m_time    = time;

    // Resize component and tracker vectors to match number of
    // each in the particle model.
    m_comp.resize(model.ComponentCount(), 0.0);
    m_values.resize(model.TrackerCount(), 0.0);
}

// Copy constructor.
AggModels::Primary::Primary(const Primary &copy)
{
    init();
    *this = copy;
}

// Stream-reading constructor.
AggModels::Primary::Primary(std::istream &in, const Sweep::ParticleModel &model)
{
    Deserialize(in, model);
}

// Default destructor.
AggModels::Primary::~Primary()
{
    releaseMem();
}

// OPERATOR OVERLOADS.

//! Assignment operator.
AggModels::Primary &AggModels::Primary::operator=(const Primary &rhs)
{
    if (this != &rhs) {
        // Check if the RHS uses the same particle model before copying
        // components and tracker values.
        if (rhs.m_pmodel == m_pmodel) {
            // Copy composition.
            memcpy(&m_comp[0], &rhs.m_comp[0], sizeof(double)*m_comp.size());
            // Copy tracker variables.
            memcpy(&m_values[0], &rhs.m_values[0], sizeof(double)*m_values.size());
        } else {
            // Set the particle model.
            m_pmodel = rhs.m_pmodel;
            // Copy components.
            m_comp.assign(rhs.m_comp.begin(), rhs.m_comp.end());
            // Copy tracker variables.
            m_values.assign(rhs.m_values.begin(), rhs.m_values.end());
        }

        // Copy primary time.
        m_createt = rhs.m_createt;
        m_time    = rhs.m_time;

        //! Copy derived and basic properties.
        m_diam = rhs.m_diam;
        m_dcol = rhs.m_dcol;
        m_dmob = rhs.m_dmob;
        m_surf = rhs.m_surf;
        m_vol  = rhs.m_vol;
        m_mass = rhs.m_mass;
        m_numcarbon = rhs.m_numcarbon;
		m_numOf6Rings = rhs.m_numOf6Rings;
        m_frag = rhs.m_frag;
		m_phaseterm = rhs.m_phaseterm;
    }
    return *this;
}

// DEFINING PARTICLE MODEL.

// Returns the particle model used to create this primary.
const Sweep::ParticleModel *const AggModels::Primary::ParticleModel(void) const
{
    return m_pmodel;
}

// PARTICLE COMPOSITION.

//Returns named component
double AggModels::Primary::GetComponent(std::string name) const
{
	int index = m_pmodel->ComponentIndex(name);
	return Composition(index);
}

// Returns the composition vector.
const fvector &AggModels::Primary::Composition() const
{
	return m_comp;
}

// Returns the ith component value.  Returns 0.0 if i is invalid.
double AggModels::Primary::Composition(unsigned int i) const
{
	if (i < m_comp.size()) {
		return m_comp[i];
	} else {
		return 0.0;
	}
}

// Sets the composition vector.
void AggModels::Primary::SetComposition(const Sweep::fvector &comp)
{
	m_comp.assign(comp.begin(), comp.end());
}

// TRACKER VARIABLE VALUES.

// Returns the tracker value vector.
const fvector &AggModels::Primary::Values() const
{
	return m_values;
}

// Returns the ith tracker variable value.  Returns 0.0 if i is invalid.
double AggModels::Primary::Values(unsigned int i) const
{
	if (i < m_values.size()) {
		return m_values[i];
	} else {
		return 0.0;
	}
}

// Sets the values vector.
void AggModels::Primary::SetValues(const fvector &vals)
{
    m_values.assign(vals.begin(), vals.end());
}

// Sets the ith trackervalue.
void AggModels::Primary::SetValue(unsigned int i, double val)
{
	if (i < m_values.size()) {
		m_values[i] = val;
	}
}

// PHASE VARIABLES

// Get the mass of a specific phase
double AggModels::Primary::GetPhaseMass(int i) const{

	// Get component indices for phase
	std::vector<unsigned int> indices = m_pmodel->GetPhaseComponents(i);

	// Loop over composition and calculate mass and volume.
	double mass = 0.0;
	for (std::vector<unsigned int>::const_iterator it = indices.begin(); it != indices.end(); ++it){
		mass += m_pmodel->Components((*it))->MolWt() * m_comp[(*it)] / NA;
	}

	return mass;
}

// PRIMARY CREATE TIME.

// Returns the primary create time.
double AggModels::Primary::CreateTime() const {return m_createt;}

// LAST UPDATE TIME.

// Returns the primary last update time.
double AggModels::Primary::LastUpdateTime() const {return m_time;}

// Sets the last update time of the primary.
void AggModels::Primary::SetTime(double t) {m_time = t;}

// AGGREGATION MODEL.

// Returns the aggregation model which this primary describes.
AggModels::AggModelType AggModels::Primary::AggID(void) const {return AggModels::Spherical_ID;}

// BASIC DERIVED PARTICLE PROPERTIES.

//! Calculates the derived properties from the unique properties.
void AggModels::Primary::UpdateCache(void)
{
    double m = 0.0;
    int m_numcarbon_temp = 0;

    // Loop over composition and calculate mass and volume.
    m_mass = m_vol = 0.0;
    for (unsigned int i=0; i!=m_pmodel->ComponentCount(); ++i) {
        m = m_pmodel->Components(i)->MolWt() * m_comp[i] / NA;
        m_mass += m;
        if (m_pmodel->Components(i)->Density() > 0.0)
            m_vol  += m / m_pmodel->Components(i)->Density();
    }

    /**
     * Get the number of carbon atoms.
     * It is assumed that the 0th index of m_comp contains the number of carbon
     * atoms.
     */
    m_numcarbon = 0;
    m_numcarbon = m_comp[0];

    /**
     * For particles with one carbon atom, set the number of carbon atoms to 0.
     * m_numcarbon is used to calculate the rate of fragmentation. Particles
     * with an m_numcarbon of 1 cannot further fragment; therefore, to exclude
     * these particles from the rate calculation m_numcarbon is set to 0.
     */
    if(m_numcarbon < 2) {
        m_numcarbon = 0;
    }

    /**
     * Indicate that a particle with one carbon atom cannot fragment.
     * m_frag is used in the uniform selection of particle for fragmentation.
     * By default, a particle is able to fragment.
     */
    m_frag = 0;
    if(m_numcarbon >= 2) {
        m_frag = 1;
    }

    // Calculate other properties (of sphere).
    m_diam = pow(6.0 * m_vol / PI, ONE_THIRD);
    m_dcol = m_diam;
    m_dmob = m_diam;
    m_surf = PI * m_diam * m_diam;
    m_numcarbon = 0;
    for (unsigned int i=0; i!=m_pmodel->ComponentCount(); ++i) {
        m_numcarbon_temp = m_comp[i];
        m_numcarbon += m_numcarbon_temp;
    }

	// phaseterm for kinetic titania phase transformation
	// Components An and Ru assumed, 0 if components don't exist
	double An = AggModels::Primary::GetComponent("An");
	double Ru = AggModels::Primary::GetComponent("Ru");
	m_phaseterm = pow( An, TWO_THIRDS) * pow((An + Ru), ONE_THIRD);
}

// Returns the particle equivalent sphere diameter.
double AggModels::Primary::SphDiameter(void) const {return m_diam;}

// Returns the collision diameter.
double AggModels::Primary::CollDiameter(void) const {return m_dcol;}

// Rethrns the mobility diameter.
double AggModels::Primary::MobDiameter(void) const {return m_dmob;}

// Returns the surface area.
double AggModels::Primary::SurfaceArea(void) const {return m_surf;}

// Returns the equivalent sphere surface area, based
// on the volume.
double AggModels::Primary::SphSurfaceArea(void) const
{
    return PI * m_diam * m_diam;
}

// Returns the volume.
double AggModels::Primary::Volume(void) const {return m_vol;}

// Returns the mass.
double AggModels::Primary::Mass(void) const {return m_mass;}

//! Returns the number of carbon atoms.
int AggModels::Primary::NumCarbon(void) const {return m_numcarbon;}

//! Returns fragmentation flag.
int AggModels::Primary::Frag(void) const {return m_frag;}
int AggModels::Primary::NumRings(void) const { return m_numOf6Rings; }

//! Returns the property with the given ID.
double AggModels::Primary::Property(const Sweep::PropID id) const
{
    switch (id) {
        case iDsph:      // Equivalent sphere diameter.
            return m_diam;
        case iDcol:   // Collision diameter.
            return m_dcol;
        case iDmob:   // Mobility diameter.
            return m_dmob;
        case iS:      // Surface area.
            return m_surf;
        case iASN:      // Surface area.
            return GetSites();
        case iV:      // Volume.
            return m_vol;
        case iM:      // Mass.
            return m_mass;
        case iNumCarbon:        //! Number of carbon atoms.
            return m_numcarbon;
        case iFrag:             //! Fragmentation flag.
            return m_frag;
        default:
            return 0.0;
    }
}

// BASIC DERIVED PROPERTY OVERWRITES.

// Sets the spherical particle diameter
void AggModels::Primary::SetSphDiameter(double diam) {m_diam = diam;}

// Sets the collision diameter of the particle.
void AggModels::Primary::SetCollDiameter(double dcol) {m_dcol = dcol;}

// Sets the mobility diameter.
void AggModels::Primary::SetMobDiameter(double dmob) {m_dmob = dmob;}

// Sets the surface area, subject to minimum spherical area condition.
void AggModels::Primary::SetSurfaceArea(double surf) {m_surf = surf;}

// Sets the volume.
void AggModels::Primary::SetVolume(double vol) {m_vol = vol;}

// Sets the mass.
void AggModels::Primary::SetMass(double m) {m_mass = m;}

//! Sets the number of carbon atoms.
void AggModels::Primary::SetNumCarbon(int numcarbon) {m_numcarbon = numcarbon;}

//! Sets fragmentation flag.
void AggModels::Primary::SetFrag(int frag) {m_frag = frag;}
/*!
 * Check that this primary is a physically valid particle.  This currently
 * means checking that the amount of each component is within a range specifed
 * for that component.
 *
 *@return   True iff primary particle is valid as a physical particle
 */
bool AggModels::Primary::IsValid() const {
    for(unsigned int i = 0; i < m_comp.size(); ++i) {
        // Check each component value, but stop as soon as an invalid value
        // is found
        if(!m_pmodel->Components(i)->IsValidValue(m_comp[i]))
            return false;
    }

    // Check one of the cached values, all the m_??? could be added here
    // if necessary.
    return (m_mass > 0);
}

// OPERATIONS.

// Adjusts the primary with the given composition and
// tracker values changes n times.  If the particle cannot be adjust
// n times, then this function returns the number of times
// it was adjusted.
unsigned int AggModels::Primary::Adjust(const fvector &dcomp, const fvector &dvalues, rng_type &rng, unsigned int n)
{
	unsigned int i = 0;

	n = CalculateMaxAdjustments(dcomp, n);

	// Add the components.
	for (i=0; i!=min(m_comp.size(),dcomp.size()); ++i) {
		m_comp[i] += dcomp[i] * (double)n;
	}

	// Add the tracker values.
	for (i=0; i!=min(m_values.size(),dvalues.size()); ++i) {
		m_values[i] += dvalues[i] * (double)n;
	}

    // Update property cache.
	if (n > 0) {
		AggModels::Primary::UpdateCache();
	}

    return n;
}

/*!
 * Calculates the maximum number of LPDA adjustments allowed.
 *
 * This is relevant for Processes which remove components from a Primary,
 * particularly where multiple components are present in a particle.
 *
 * @param dcomp     The component changes for the Process
 * @param n         Number of times requested
 * @return          Actual number of times allowed
 */
unsigned int AggModels::Primary::CalculateMaxAdjustments(
        const fvector &dcomp,
        unsigned int n) const
{
    unsigned int i;

    // First check if there is a negative in dcomp
    bool neg_change(false);
    for (i=0; i!=dcomp.size(); ++i) {
        if (dcomp[i] < 0.0) neg_change = true;
    }

    // Calculate the actual number of events only if there is a negative
    // change in the component amount
    if (neg_change) {
        for (i=0; i!=min(m_comp.size(),dcomp.size()); ++i) {
            if (dcomp[i] < 0.0)
                n = std::min(n, (unsigned int) abs(m_comp[i]/dcomp[i]));
        }
    }

    return n;
}

/*!
* Calculates the maximum possible number of adjustments
*
* This is relevant for Processes which remove components from a Primary,
* particularly where multiple components are present in a particle.
*
* @param dcomp     The component changes for the Process
* @return          Number of times allowed
*/
unsigned int AggModels::Primary::CalculateMaxAdjustments(const fvector &dcomp) const
{
	unsigned int i;
	unsigned int n = 0;

	// First check if there is a negative in dcomp
	bool neg_change(false);
	for (i = 0; i != dcomp.size(); ++i) {
		if (dcomp[i] < 0.0) {
			neg_change = true;
			n = std::numeric_limits<unsigned int>::max();
		}
	}

	// Calculate the actual number of events only if there is a negative
	// change in the component amount
	if (neg_change) {
		for (i = 0; i != min(m_comp.size(), dcomp.size()); ++i) {
			if (dcomp[i] < 0.0)
				n = std::min(n, (unsigned int)abs(m_comp[i] / dcomp[i]));
		}
	}

	return n;
}

// Adjusts the particle n times for IntParticle reaction
unsigned int AggModels::Primary::AdjustIntPar(const fvector &dcomp, const fvector &dvalues, rng_type &rng, unsigned int n)
{
    n = AggModels::Primary::Adjust(dcomp, dvalues, rng, n);

    return n;
}

//Adjusts the particle n times for the phase transformation process
unsigned int AggModels::Primary::AdjustPhase(const fvector &dcomp,
                              const fvector &dvalues,
                              rng_type &rng,
							  unsigned int n)
{
	n = AggModels::Primary::Adjust(dcomp, dvalues, rng, n);

    return n;
}

//Thermodynamic phase transformation (melting model)
void AggModels::Primary::Melt(rng_type &rng, Cell &sys)
{

	fvector dcomp(m_pmodel->ComponentCount(), 0.0);
	std::vector<fvector> all_dcomp;
	fvector dvalues(m_pmodel->TrackerCount(), 0.0); //for now the transformation model doesn't adjust the tracker variables
	unsigned int n = 0;

	//check if liquid
	bool Liquid = ParticleModel()->MeltModel().IsLiquid(sys, *this);

	//has the particle melted?
	if (Liquid == true){
		// melt the particle
		ParticleModel()->MeltModel().MeltingCompositionChange(all_dcomp);
	}
	else{ //particle is solid
		//total composition excluding liquid phase
		double mass = 0.0;
		fvector phaseMass(m_pmodel->PhaseCount(), 0.0);

		//add all components excluding liquid components
		for (unsigned int i = 0; i < m_pmodel->PhaseCount(); i++){
			if (!m_pmodel->PhaseIsLiquid(i)){
				phaseMass[i] = GetPhaseMass(i);
				mass += phaseMass[i];
			}
		}

		if (mass > 0.0){
			//if solid phases exist then convert liquid to solid phase probabilistically
			//weighted by mass of existing solid phases
			boost::uniform_01<rng_type&, double> uniformGenerator(rng);
			double j = uniformGenerator() * mass; //generate random number
			for (unsigned int i = 0; i < m_pmodel->PhaseCount(); i++){

				if (j <= phaseMass[i]){
					//get change in composition
					ParticleModel()->MeltModel().CompositionChange(i, all_dcomp);
					break;
				}
				else{
					j -= phaseMass[i];
				}
			}
		}
		else{
			//if everything is liquid then tranform based on melting model
			ParticleModel()->MeltModel().CompositionChange(sys, *this, all_dcomp); //get change in composition
		}
	}

	//loop over all composition changes and transform components
	for (std::vector<fvector>::const_iterator it = all_dcomp.begin(); it != all_dcomp.end(); ++it){

		dcomp = *it;

		// transform components
		n = Primary::CalculateMaxAdjustments(dcomp); //transform everything: get max adjustment
		if (n > 0){ n = Primary::Adjust(dcomp, dvalues, rng, n); }; //adjust primary
	}
}

// Property for titania phase transformation model
double AggModels::Primary::GetPhaseTerm(void) const
{
	return m_phaseterm;
}

/*!
 *  Combines this primary with another.
 *
 *  Note the very strange behaviour when the primaries do not
 *  have equal particle model pointers.  Users should also
 *  be very careful about not mixing different types that inherit
 *  from PrimaryParticle.
 *
 * \param[in]       rhs         Primary particle to add to current instance
 * \param[in,out]   rng         Random number generator
 *
 * \return      Reference to the current instance after rhs has been added
 */
AggModels::Primary &AggModels::Primary::Coagulate(const Primary &rhs, rng_type &rng)
{
    // Check if the RHS uses the same particle model.  If not, then
    // just use the assignment operator because you can't add apples
    // and bananas!
    if (rhs.m_pmodel == m_pmodel) {
        // Add the components.
        for (unsigned int i=0; i!=min(m_comp.size(),rhs.m_comp.size()); ++i) {
            m_comp[i] += rhs.m_comp[i];
        }

        // Add the tracker values.
        for (unsigned int i=0; i!=min(m_values.size(),rhs.m_values.size()); ++i) {
            m_values[i] += rhs.m_values[i];
        }

        // Create time is the earliest time.
        m_createt = min(m_createt, rhs.m_createt);
        m_time    = min(m_time, rhs.m_time);
    } else {
        // Different particle models!
        *this = rhs;
        std::cerr << "Sweep::AggModels::Primary::Coagulate called for particles with models " << m_pmodel
                  << " and " << rhs.m_pmodel << std::endl;
    }
    AggModels::Primary::UpdateCache();
    return *this;
}

/*!
 *  Combines this primary with another.
 *
 *  Note the very strange behaviour when the primaries do not
 *  have equal particle model pointers.  Users should also
 *  be very careful about not mixing different types that inherit
 *  from PrimaryParticle.
 *
 * \param[in]       rhs         Primary particle to add to current instance
 * \param[in,out]   rng         Random number generator
 *
 * \return      Reference to the current instance after rhs has been added
 */
AggModels::Primary &AggModels::Primary::Fragment(const Primary &rhs, rng_type &rng)
{
    // Check if the RHS uses the same particle model.  If not, then
    // just use the assignment operator because you can't add apples
    // and bananas!
    if (rhs.m_pmodel == m_pmodel) {
        // Add the components.
        for (unsigned int i=0; i!=min(m_comp.size(),rhs.m_comp.size()); ++i) {
            m_comp[i] += rhs.m_comp[i];
        }

        // Add the tracker values.
        for (unsigned int i=0; i!=min(m_values.size(),rhs.m_values.size()); ++i) {
            m_values[i] += rhs.m_values[i];
        }

        // Create time is the earliest time.
        m_createt = min(m_createt, rhs.m_createt);
        m_time    = min(m_time, rhs.m_time);
    } else {
        // Different particle models!
        *this = rhs;
        std::cerr << "Sweep::AggModels::Primary::Fragment called for particles with models " << m_pmodel
                  << " and " << rhs.m_pmodel << std::endl;
    }
    AggModels::Primary::UpdateCache();
    return *this;
}

// This routine sinters the Primary for the given length of
// time using the provided sintering model.
void AggModels::Primary::Sinter(double dt, Cell &sys,
                     const Processes::SinteringModel &model,
                     rng_type &rng,
                     double wt)
{
    // Spherical primaries don't sinter.

    return;
}

/*!
 * @brief       Gets the ratio of second to first component number
 *
 * A coarse estimation of the coverage fraction; as the amount of the second
 * component relative to the amount of the first. Returns 1.0 for single comp.
 *
 * @return      Ratio of number of second to first component
 */
double AggModels::Primary::GetCoverageFraction() const
{
    double val = 1.0;

    if (m_pmodel->ComponentCount() > 1 && m_comp[0] > 0.0) {
        val = m_comp[1]/m_comp[0];
    }

    return val;
}

// READ/WRITE/COPY.

// Returns a copy of the model data.
AggModels::Primary *const AggModels::Primary::Clone(void) const
{
    return new Primary(*this);
}

//! Writes the object to a binary stream.
void AggModels::Primary::Serialize(std::ostream &out) const
{
    if (out.good()) {
        // Output the version ID (=0 at the moment).
        const unsigned int version = 0;
        out.write((char*)&version, sizeof(version));

        // Write number of components.
        unsigned int n = (unsigned int)m_comp.size();
        out.write((char*)&n, sizeof(n));

        // Write components.
        double val = 0.0;
        for (unsigned int i=0; i!=n; ++i) {
            val = (double)m_comp[i];
            out.write((char*)&val, sizeof(val));
        }

        // Write number of tracker values.
        n = (unsigned int)m_values.size();
        out.write((char*)&n, sizeof(n));

        // Write values.
        for (unsigned int i=0; i!=n; ++i) {
            val = (double)m_values[i];
            out.write((char*)&val, sizeof(val));
        }

        // Write create time.
        val = (double)m_createt;
        out.write((char*)&val, sizeof(val));

        // Write last update time.
        val = (double)m_time;
        out.write((char*)&val, sizeof(val));

        // Write equivalent sphere diameter.
        val = (double)m_diam;
        out.write((char*)&val, sizeof(val));

        // Write collision diameter.
        val = (double)m_dcol;
        out.write((char*)&val, sizeof(val));

        // Write mobility diameter.
        val = (double)m_dmob;
        out.write((char*)&val, sizeof(val));

        // Write surface area.
        val = (double)m_surf;
        out.write((char*)&val, sizeof(val));

        // Write volume.
        val = (double)m_vol;
        out.write((char*)&val, sizeof(val));

        // Write mass.
        val = (double)m_mass;
        out.write((char*)&val, sizeof(val));

        //! Write number of carbon atoms.
        val = (int)m_numcarbon;
        out.write((char*)&val, sizeof(val));

        //! Write fragmentation flag.
        val = (int)m_frag;
        out.write((char*)&val, sizeof(val));

		// Write phaseterm.
        val = (double)m_phaseterm;
        out.write((char*)&val, sizeof(val));
    } else {
        throw invalid_argument("Output stream not ready "
                               "(Sweep, AggModels::Primary::Serialize).");
    }
}

//! Reads the object from a binary stream.
void AggModels::Primary::Deserialize(std::istream &in, const Sweep::ParticleModel &model)
{
    releaseMem();
    m_pmodel = &model;

    if (in.good()) {
        // Read the output version.  Currently there is only one
        // output version, so we don't do anything with this variable.
        // Still needs to be read though.
        unsigned int version = 0;
        in.read(reinterpret_cast<char*>(&version), sizeof(version));

        unsigned int n = 0;
        double val = 0.0;

        switch (version) {
            case 0:
                // Read number of components.
                in.read(reinterpret_cast<char*>(&n), sizeof(n));

                // Read components.
                for (unsigned int i=0; i!=n; ++i) {
                    in.read(reinterpret_cast<char*>(&val), sizeof(val));
                    m_comp.push_back((double)val);
                }

                // Read number of tracker values.
                in.read(reinterpret_cast<char*>(&n), sizeof(n));

                // Read values.
                for (unsigned int i=0; i!=n; ++i) {
                    in.read(reinterpret_cast<char*>(&val), sizeof(val));
                    m_values.push_back((double)val);
                }

                // Read create time.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_createt = (double)val;

                // Read last update time.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_time = (double)val;

                // Read equivalent sphere diameter.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_diam = (double)val;

                // Read collision diameter.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_dcol = (double)val;

                // Read mobility diameter.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_dmob = (double)val;

                // Read surface area.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_surf = (double)val;

                // Read volume.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_vol = (double)val;

                // Read mass.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_mass = (double)val;

                //! Read number of carbon atoms.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_numcarbon = (int)val;

                //! Read fragmentation flag.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_frag = (int)val;

				// Read phaseterm.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_phaseterm = (double)val;

                break;
            default:
                throw runtime_error("Serialized version number is invalid "
                                    "(Sweep, AggModels::Primary::Deserialize).");
        }
    } else {
        throw invalid_argument("Input stream not ready "
                               "(Sweep, AggModels::Primary::Deserialize).");
    }
}

// DATA MANAGEMENT.

// Release all memory associated with object.
void AggModels::Primary::releaseMem(void)
{
    m_comp.clear();
    m_values.clear();
}

//! Initialisation routine.
void AggModels::Primary::init(void)
{
    m_pmodel = NULL;
    m_createt = 0.0;
    m_time = 0.0;
    m_diam = 0.0; // Equivalent spherical diameter.
    m_dcol = 0.0; // Collision diameter.
    m_dmob = 0.0; // Mobility diameter.
    m_surf = 0.0; // Surface area.
    m_vol = 0.0;  // Volume.
    m_mass = 0.0; // Mass.
    m_numcarbon = 0;
	m_numOf6Rings = 0;
    m_frag = 0;      //!< Fragmentation flag.
	m_phaseterm = 0.0; // Phase term.
    releaseMem();
}

//! Check whether the number of carbon atoms in the primary is equal to that of
//! the inception species.
int AggModels::Primary::InceptedPAH() const
{
    std::vector<ParticleModel::PostProcessStartingStr> str_list = ParticleModel()->InceptedPAH();
	for (std::vector<int>::size_type ii = 0; ii!=str_list.size(); ii++){
		ParticleModel::PostProcessStartingStr str = str_list[ii];
		//A1, A1CH3, A2, A4, A4CH3, R5A3, A5
		switch (str){
			case ParticleModel::A1:
				if (NumCarbon() == BENZENE_C)
					return 1;
				break;
			case ParticleModel::A1CH3:
				if (NumCarbon() == TOLUENE_C)
					return 1;
				break;
			case ParticleModel::A2:
				if (NumCarbon() == NAPHTHALENE_C)
					return 1;
				break;
			case ParticleModel::A4:
				if (NumCarbon() == PYRENE_C)
					return 1;
				break;
			case ParticleModel::A4CH3:
				if (NumCarbon() == METHYLPYRENE_C)
					return 1;
				break;
			case ParticleModel::R5A3:
				if (NumCarbon() == MPHENANTHRENER_C)
					return 1;
				break;
			case ParticleModel::A5:
				if (NumCarbon() == BENZOPYRENE_C)
					return 1;
				break;
			default:
				return 0;
		}
	}
	return 0;
}

//! Returns the frame position and orientation, and primary coordinates
//	Used by bintree_primary for particle tracking
void AggModels::Primary::GetFrameCoords(std::vector<fvector> &coords) const
{
	//unused
}

void AggModels::Primary::setTracking()
{
	//spherical particles don't have primaries
}

void AggModels::Primary::removeTracking()
{
	//spherical particles don't have primaries
}


/*
// Copyright (c) 2000-2009, Texas Engineering Experiment Station (TEES), a
// component of the Texas A&M University System.

// All rights reserved.

// The information and source code contained herein is the exclusive
// property of TEES and may not be disclosed, examined or reproduced
// in whole or in part without explicit written authorization from TEES.
*/

#ifndef STAPL_CONTAINERS_GRAPH_ALGORITHMS_GRAPH_COLORING_HPP
#define STAPL_CONTAINERS_GRAPH_ALGORITHMS_GRAPH_COLORING_HPP

#include <stapl/algorithms/algorithm.hpp>
#include <stapl/containers/graph/views/graph_view.hpp>
#include <stapl/views/native_view.hpp>
#include <stapl/views/array_view.hpp>
#include <stapl/views/repeated_view.hpp>
#include <stapl/containers/array/static_array.hpp>
#include <stapl/domains/interval.hpp>
#include <stapl/containers/partitions/explicit.hpp>
#include <boost/unordered_map.hpp>
#include <vector>
#include <utility>

namespace stapl {

namespace graph_coloring_impl {

//////////////////////////////////////////////////////////////////////
/// @brief Functor to extract color of a remote-neighbor vertex.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct get_remote_neighbor_colors
{
  typedef boost::unordered_map<size_t, size_t> result_type;

private:
  size_t m_key;

public:
  get_remote_neighbor_colors(size_t const& key)
    : m_key(key)
  { }

  template <typename HashMap>
  result_type operator()(HashMap& hmap) const
  {
    typename HashMap::iterator it;
    it = hmap.find(m_key);

    if (it == hmap.end())
      return result_type();

    return it->second;
  }

  void define_type(typer& t)
  {
    t.member(m_key);
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Functor to store received color from a neighbor vertex.
/// Used by @ref set_remote_neighbor_colors_of_vertex.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct set_remote_neighbor_colors
{
  typedef void result_type;

private:
  typedef std::pair<size_t, size_t> neighbor_color_type;

  size_t              m_vid;
  neighbor_color_type m_neighbor_color;

public:
  set_remote_neighbor_colors(size_t const& vid,
                             neighbor_color_type const& neighbor_color)
    : m_vid(vid), m_neighbor_color(neighbor_color)
  { }

  template <typename HashMap>
  void operator()(HashMap& hmap) const
  {
    hmap[m_vid][m_neighbor_color.first] = m_neighbor_color.second;
  }

  void define_type(typer& t)
  {
    t.member(m_vid);
    t.member(m_neighbor_color);
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to inform the neighbors of a vertex of its color
/// using @ref set_remote_neighbor_colors.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
template <typename HashMap>
class set_remote_neighbor_colors_of_vertex
{
public:
  typedef std::pair<size_t, size_t> neighbor_color_type;
  typedef void                      result_type;

private:
  neighbor_color_type               m_neighbor_color;
  p_object_pointer_wrapper<HashMap> m_hashmap;

public:
  set_remote_neighbor_colors_of_vertex(
    neighbor_color_type const& neighbor_color,
    HashMap& hashmap)
    : m_neighbor_color(neighbor_color),
      m_hashmap(&hashmap)
  { }

  template <typename Vertex>
  void operator()(Vertex&& v) const
  {
    m_hashmap->apply_set(
      m_hashmap->get_location_id(),
      set_remote_neighbor_colors(v.descriptor(), m_neighbor_color)
    );
  }

  void define_type(typer& t)
  {
    t.member(m_neighbor_color);
    t.member(m_hashmap);
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Function to pick a color that does not conflict with the
/// provided colors. Used to pick a safe color for a vertex.
/// @param neighbor_colors A domain specifying the colors of all neighbors
/// of the vertex in question.
/// @return A safe color that can be assigned to the vertex without creating
/// conflicts with the input set of colors.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
inline size_t pick_vertex_color(domset1D<size_t> const& neighbor_colors)
{
  size_t num_colors = neighbor_colors.size();
  domset1D<size_t> contiguous_dom(num_colors);
  contiguous_dom -= neighbor_colors;
  if (contiguous_dom.empty())
    return num_colors;
  else
    return contiguous_dom.first();
}

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to compute graph coloring.
/// Works over a partition of the graph to compute the coloring for
/// each partition independently. The coloring for border vertices is
/// is then adjusted to reflect any colors received from neighbors in
/// a different partition.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct graph_coloring_wf
{
  typedef void result_type;

  template <typename Partition, typename GView, typename ColorMap,
            typename Array>
  void operator()(Partition const& p, GView& gview, ColorMap& color_map,
                  Array& hashmap_array) const
  {
    typedef boost::unordered_map<size_t, size_t> color_boundary_map_type;
    typename Partition::const_vertex_iterator it = p.begin(),
                                          end_it = p.end();
    for (; it!=end_it; ++it) {
      bool boundary_vertex = false;
      domset1D<size_t> neighbor_colors;
      std::vector<size_t> remote_neighbors;
      size_t vid = (*it).descriptor();
      typename Partition::const_vertex_iterator::reference::adj_edge_iterator
        edge_it = (*it).begin(), edge_end_it = (*it).end();
      for (; edge_it!=edge_end_it; ++edge_it) {
        size_t target = (*edge_it).target();

        //Get colors of local neighbors
        if (p.domain().contains(target) && (target != vid)) {
          neighbor_colors += color_map.get(target);
        } else {
          remote_neighbors.push_back(target);
          boundary_vertex = true;
        }
      } //endfor over edges

      //Check received colors of remote neighbors
      if (boundary_vertex) {
        color_boundary_map_type bmap =
          hashmap_array.apply_get(
            hashmap_array.container().get_location_id(),
            get_remote_neighbor_colors(vid)
          );
        color_boundary_map_type::iterator map_it = bmap.begin(),
                                          map_end_it = bmap.end();
        for (; map_it!=map_end_it; ++map_it)
          neighbor_colors += map_it->second;
      }

      size_t mycolor = pick_vertex_color(neighbor_colors);
      //set my color
      color_map.put(vid, mycolor);

      //send my color to neighbors
      if (boundary_vertex) {
        std::vector<size_t>::iterator vect_it = remote_neighbors.begin(),
                                      vect_end_it = remote_neighbors.end();
        for (; vect_it!=vect_end_it; ++vect_it) {
          typedef typename view_traits<Array>::container hashmap_type;

          gview.apply_set(*vect_it,
                          set_remote_neighbor_colors_of_vertex<hashmap_type>(
                            std::make_pair(vid, mycolor),
                            hashmap_array.container()));
        }
      }
    } //endfor over the vertices
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to return if the coloring stored in the provided
/// vertex property map (color_map) is valid for the input graph.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct is_valid_graph_coloring_wf
{
  typedef bool result_type;

  template <typename Vertex, typename ColorMap>
  bool operator()(Vertex const& v, ColorMap& color_map) const
  {
    size_t vid = v.descriptor();
    size_t mycolor = color_map.get(v);
    typename Vertex::const_adj_edge_iterator edge_it = v.begin(),
                                             edge_end_it = v.end();
    for (; edge_it!=edge_end_it; ++edge_it)
    {
      size_t target = (*edge_it).target();
      if ((color_map.get(target) == mycolor) && (target != vid))
        return false;
    }
    return true;
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to return the color of a vertex.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct get_vertex_color
{
  typedef domset1D<size_t> result_type;

  template <typename Vertex, typename ColorMap>
  result_type operator()(Vertex const& v, ColorMap& color_map) const
  {
    size_t mycolor = color_map.get(v);
    return result_type(mycolor, mycolor);
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to merge two vectors. Used in reduce.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
template<typename T>
struct reduce_vector_wf
{
  typedef std::vector<T> result_type;
  template<typename Element1, typename Element2>
  result_type operator()(Element1 const& elt1, Element2 const& elt2)
  {
    result_type result = elt1;
    std::copy(elt2.begin(), elt2.end(), std::back_inserter(result));
    return result;
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to return the sub-domain of the elements in
/// the hash-map.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
template <typename HashMap, typename DomainType>
struct get_sub_domain
{
  typedef std::vector<DomainType> result_type;
  template<typename Element>
  result_type operator()(Element const& elt)
  {
    HashMap hmap = elt;
    DomainType dom;
    typename HashMap::iterator it = hmap.begin(),
                           end_it = hmap.end();
    for (; it!=end_it; ++it)
    {
      dom += it->first;
    }
    return result_type(1, dom);
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to resolve color conflicts between boundary
/// vertices that end up with the same colors despite being neighbors.
/// In such cases, one of the vertices is re-assigned its colors based
/// on the next safe color it can use.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct resolve_color_conflicts
{
  typedef bool result_type;

  template <typename Partition, typename GView, typename ColorMap,
            typename Array>
  result_type operator()(Partition const& p, GView& gview, ColorMap& color_map,
                         Array& hashmap_array) const
  {
    typedef boost::unordered_map<size_t, size_t> color_boundary_map_type;
    bool partition_conflict = false;
    typedef typename Partition::const_vertex_iterator const_vertex_iterator;
    const_vertex_iterator it = p.begin(), end_it = p.end();
    for (; it!=end_it; ++it) {
      bool responsible_for_conflict = false;
      domset1D<size_t> neighbor_colors;
      size_t vid = (*it).descriptor();
      size_t mycolor = color_map.get(vid);
      color_boundary_map_type bmap =
        hashmap_array.apply_get(
          hashmap_array.container().get_location_id(),
          get_remote_neighbor_colors(vid)
        );

      color_boundary_map_type::iterator map_it = bmap.begin(),
                                        map_end_it = bmap.end();
      //Compare my color with remote neighbors' colors
      for (; map_it!=map_end_it; ++map_it) {
        size_t neighbor_id = map_it->first;
        size_t neighbor_color = map_it->second;
        neighbor_colors += neighbor_color;
        if (mycolor == neighbor_color && (vid < neighbor_id)) {
          partition_conflict = true;
          responsible_for_conflict = true;
        }
      }

      if (responsible_for_conflict) {
        const auto p = *it;

        typename decltype(p)::const_adj_edge_iterator
          edge_it = p.begin(), edge_end_it = p.end();
        for (; edge_it!=edge_end_it; ++edge_it) {
          size_t target = (*edge_it).target();

          //Get colors of local neighbors
          if (!bmap.count(target) && (target != vid)) {
            neighbor_colors += color_map.get(target);
          }
        }

        size_t new_color = pick_vertex_color(neighbor_colors);

        //set my color
        color_map.put(vid, new_color);

        //send my new color to remote neighbors
        map_it = bmap.begin();
        for (; map_it!=map_end_it; ++map_it) {
          typedef typename view_traits<Array>::container hashmap_type;

          gview.apply_set(map_it->first,
                          set_remote_neighbor_colors_of_vertex<hashmap_type>(
                            std::make_pair(vid, new_color),
                            hashmap_array.container()));
        }
      }
    }
    return partition_conflict;
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to populate an output undirected graph with
/// vertices from the directed graph.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct build_vertex_undirected_graph_wf
{
  typedef void result_type;

  template <typename Vertex,typename UGraph>
  void operator()(Vertex v, UGraph& ugraph) const
  {
    ugraph.add_vertex(v.descriptor(), typename UGraph::vertex_property());
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Work-function to create an undirected version of a directed graph.
/// Adds bi-directional edges for each directed edge in the input graph.
/// @ingroup pgraphAlgoDetails
//////////////////////////////////////////////////////////////////////
struct build_edge_undirected_graph_wf
{
  typedef void result_type;

  template <typename Vertex,typename UGraph>
  void operator()(Vertex v, UGraph& ugraph) const
  {
    typedef typename UGraph::edge_descriptor edge_descriptor;
    typename Vertex::adj_edge_iterator edge_it = v.begin(),
                                       edge_end_it = v.end();
    for (; edge_it!=edge_end_it; ++edge_it)
    {
      ugraph.add_edge_async(edge_descriptor((*edge_it).source(),
                                            (*edge_it).target()));
    }
  }
};

} // namespace graph_coloring_impl

//////////////////////////////////////////////////////////////////////
/// @brief Parallel Level-Synchronized Graph Coloring Algorithm.
///
/// Assigns coloring to the vertices of the input graph, such that
/// neighboring vertices are not assigned the same color.
/// @param graph The @ref graph_view over the input graph.
/// @param color_map The output vertex property map where the color information
/// will be stored. [vertex->color (int)]
/// Specialization for directed graphs.
/// Copies the directed graph to an undirected graph and computes coloring
/// for the undirected graph.
/// @warning  This method invalidates the input graph view.
/// @ingroup pgraphAlgo
/// @todo This could be using an @ref undirected_view over the input graph
/// instead of creating its own methods to make the undirected graph.
//////////////////////////////////////////////////////////////////////
template <template<typename, typename, typename, typename> class GView,
          template<graph_attributes, graph_attributes, typename...> class Graph,
          graph_attributes M, typename ...OptionalParams,
          typename Dom, typename MapFunc, typename Derived, typename ColorMap>
void color_graph(GView<Graph<DIRECTED, M, OptionalParams...>,
                 Dom, MapFunc, Derived> const& graph,
                 ColorMap& color_map)
{
  typedef Graph<UNDIRECTED, NONMULTIEDGES, OptionalParams...>
                                                        undirected_graph_type;
  typedef graph_view<undirected_graph_type,
                typename undirected_graph_type::domain_type,
                MapFunc,Derived>                        undirected_view_type;

  undirected_graph_type undir_graph;
  undirected_view_type undir_view(undir_graph);

  //Add vertices.
  map_func(graph_coloring_impl::build_vertex_undirected_graph_wf(),
           graph, make_repeat_view(undir_view));

  //Add undirected edges.
  map_func(graph_coloring_impl::build_edge_undirected_graph_wf(), graph,
           make_repeat_view(undir_view));

  undirected_view_type undir_view2(undir_graph);
  color_graph(undir_view2, color_map);
}

//////////////////////////////////////////////////////////////////////
/// @brief Parallel Level-Synchronized Graph Coloring Algorithm.
///
/// Assigns coloring to the vertices of the input graph, such that
/// neighboring vertices are not assigned the same color.
/// @param graph The @ref graph_view over the input graph.
/// @param color_map The output vertex property map where the color information
/// will be stored. [vertex->color (int)]
/// Specialization for @ref iterator_domain based @ref graph_view over
/// @ref dynamic_graph, because sparse-domain is needed in the algorithm to
/// have a view over the boundary vertices.
/// @ingroup pgraphAlgo
//////////////////////////////////////////////////////////////////////
template <template<typename, typename, typename, typename> class GView,
          template<graph_attributes, graph_attributes, typename...> class Graph,
          graph_attributes M, typename ...OptionalParams,
          typename Dist, typename DomMap,
          typename MapFunc, typename Derived, typename ColorMap>
void color_graph(GView<Graph<UNDIRECTED, M, OptionalParams...>,
                 iterator_domain<Dist, DomMap>, MapFunc, Derived>
                 const& graph, ColorMap& color_map)
{
  typedef Graph<UNDIRECTED, M, OptionalParams...>        undirected_graph_type;
  typedef GView<undirected_graph_type, domset1D<size_t>,
                MapFunc, Derived>                        view_type;
  typedef GView<undirected_graph_type,
                iterator_domain<Dist, DomMap>,
                MapFunc, Derived>                        original_view_type;

  //graph might have come from the directed color_graph method, which means
  //it might be invalid. The constructor below calls a map_reduce, so we need
  //to reconstruct the view in this case to make sure it is valid.
  original_view_type const& valid_graph = graph.is_valid() ?
    graph : original_view_type(
              graph.container(), graph.domain(), graph.mapfunc(), graph);

  view_type new_view(valid_graph);

  //Call graph coloring with the domset1D view
  color_graph(new_view, color_map);
}

//////////////////////////////////////////////////////////////////////
/// @brief Parallel Level-Synchronized Graph Coloring Algorithm.
///
/// Assigns coloring to the vertices of the input graph, such that
/// neighboring vertices are not assigned the same color.
/// @param graph The @ref graph_view over the input graph.
/// @param color_map The output vertex property map where the color information
/// will be stored. [vertex->color (int)]
/// @ingroup pgraphAlgo
//////////////////////////////////////////////////////////////////////
template <typename GView, typename ColorMap>
void color_graph(GView const& graph, ColorMap& color_map)
{
  typedef GView                                       graph_view;
  typedef typename graph_view::gid_type               vertex_descriptor;
  typedef typename graph_view::domain_type            domain_type;
  typedef boost::unordered_map<vertex_descriptor,
                               size_t>                vertex_color_map_type;
  typedef boost::unordered_map<vertex_descriptor,
                               vertex_color_map_type> hashmap_type;
  typedef static_array<hashmap_type>                  array_type;
  typedef array_view<array_type>                      array_view_type;

  using namespace graph_coloring_impl;
  size_t num_locs = get_num_locations();
  array_type hashmap_array(num_locs);
  array_view_type hashmap_array_view(hashmap_array);

  map_func(graph_coloring_wf(), stapl::native_view(graph),
           make_repeat_view(graph), make_repeat_view(color_map),
           make_repeat_view(hashmap_array_view));

  /*Resolve conflicts with boundary vertices*/
  //create domains of boundary vertices
  typedef explicit_partition<typename GView::domain_type>   partition_type;
  typedef segmented_view<GView,
                           partition_type>                  segment_view_type;

  std::vector<domain_type> sub_domains =
    map_reduce(get_sub_domain<hashmap_type, domain_type>(),
               reduce_vector_wf<domain_type>(),hashmap_array_view);
  segment_view_type
    boundary_view(graph, partition_type(graph.domain(), sub_domains));

  while (map_reduce(resolve_color_conflicts(), plus<bool>(), boundary_view,
                    make_repeat_view(graph), make_repeat_view(color_map),
                    make_repeat_view(hashmap_array_view)))
  { }
}

//////////////////////////////////////////////////////////////////////
/// @brief Extracts the colors from the input graph, given the color map.
///
/// @param graph The @ref graph_view over the input graph.
/// @param color_map The input vertex property map where the color information
/// is stored. [vertex->color (int)]. Must have been previously populated by
/// calling @ref color_graph().
/// @return The domain of colors in the graph.
/// @ingroup pgraphAlgo
//////////////////////////////////////////////////////////////////////
template <typename GView, typename ColorMap>
domset1D<size_t> get_graph_colors(GView const& graph, ColorMap& color_map)
{
  typedef domset1D<size_t> domain_type;
  return map_reduce(graph_coloring_impl::get_vertex_color(),
                    plus<domain_type>(), graph, make_repeat_view(color_map));
}

//////////////////////////////////////////////////////////////////////
/// @brief Computes if the given coloring is valid for the input graph.
///
/// @param graph The @ref graph_view over the input graph.
/// @param color_map The input vertex property map where the color information
/// is stored. [vertex->color (int)]. Must have been previously populated by
/// calling @ref color_graph().
/// @return True if the coloring is valid, false otherwise.
/// @ingroup pgraphAlgo
//////////////////////////////////////////////////////////////////////
template <typename GView, typename ColorMap>
bool is_valid_graph_coloring(GView const& graph, ColorMap& color_map)
{
  return map_reduce(graph_coloring_impl::is_valid_graph_coloring_wf(),
                    logical_and<bool>(), graph, make_repeat_view(color_map));
}

} // namespace stapl

#endif