/**
 ** Isaac Genome Alignment Software
 ** Copyright (c) 2010-2017 Illumina, Inc.
 ** All rights reserved.
 **
 ** This software is provided under the terms and conditions of the
 ** GNU GENERAL PUBLIC LICENSE Version 3
 **
 ** You should have received a copy of the GNU GENERAL PUBLIC LICENSE Version 3
 ** along with this program. If not, see
 ** <https://github.com/illumina/licenses/>.
 **
 ** \file BandedSmithWaterman.cpp
 **
 ** \brief See BandedSmithWaterman.hh
 **
 ** \author Come Raczy
 **/

#include <iostream>
#include <cassert>
#include <cstdlib>
#include <algorithm>
#include <boost/format.hpp>
#include <cstdint>

#include "alignment/BandedSmithWaterman.hh"

namespace isaac
{
namespace alignment
{

template <unsigned widestGapSize>
BandedSmithWaterman<widestGapSize>::BandedSmithWaterman(const int matchScore, const int mismatchScore,
                                         const int gapOpenScore, const int gapExtendScore,
                                         const int maxReadLength)
    : mismatchesMin_(gapOpenScore / -mismatchScore)
    , matchScore_(matchScore)
    , mismatchScore_(mismatchScore)
    , gapOpenScore_(gapOpenScore)
    , gapExtendScore_(gapExtendScore)
    , maxReadLength_(maxReadLength)
    , initialValue_(static_cast<int>(std::numeric_limits<short>::min()) + gapOpenScore_)
    , T_(new char[maxReadLength_ * 3 * WIDEST_GAP_SIZE * sizeof(int16_t)])
{
    // check that there won't be any overflows in the matrices
    const int maxScore = std::max(std::max(std::max(abs(matchScore_), abs(mismatchScore_)), abs(gapOpenScore_)), abs(gapExtendScore_));
    if ((maxReadLength_ * maxScore) >= abs(static_cast<int>(initialValue_)))
    {
        const std::string message = (boost::format("BandedSmithWaterman: unsupported read length (%i) for these scores (%i): use smaller scores or shorter reads") % maxReadLength_ % maxScore).str();
        BOOST_THROW_EXCEPTION(isaac::common::InvalidParameterException(message));
    }
}

template <unsigned widestGapSize>
BandedSmithWaterman<widestGapSize>::~BandedSmithWaterman()
{
    free(T_);
}

template <unsigned widestGapSize>
void BandedSmithWaterman<widestGapSize>::cp(int16_t source[WIDEST_GAP_SIZE], int16_t destination[WIDEST_GAP_SIZE]) const
{
    // AV
    for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
    destination[i] = source[i];
    }
}

template <unsigned widestGapSize>
unsigned BandedSmithWaterman<widestGapSize>::align(
    const std::vector<char> &query,
    const reference::Contig::const_iterator databaseBegin,
    const reference::Contig::const_iterator databaseEnd,
    Cigar &cigar) const
{
    return align(query.begin(), query.end(), databaseBegin, databaseEnd, cigar);
}

template <unsigned widestGapSize>
unsigned BandedSmithWaterman<widestGapSize>::trimTailIndels(Cigar& cigar, const size_t beginOffset) const
{
    unsigned ret = 0;
    uint64_t extend = 0;
    for (Cigar::Component component = Cigar::decode(cigar.back());
        cigar.size() != beginOffset; component = Cigar::decode(cigar.back()))
    {
        if (Cigar::DELETE == component.second)
        {
            //CASAVA does not like CIGAR beginning with a deletion in the data
            cigar.pop_back();
            ISAAC_ASSERT_MSG(
                Cigar::DELETE != Cigar::decode(cigar.back()).second,
                "two Cigar::DELETE cannot be next to each other");
            ret += component.first;
        }
        else if (Cigar::INSERT == component.second)
        {
            // tail and head insertions are biasing best alignment choice. remove them and extend the adjacent align operation
            extend += component.first;
            ret -= component.first;
            cigar.pop_back();
        }
        else
        {
            break;
        }
    }

    if (extend)
    {
        if(cigar.size() == beginOffset)
        {
            // this was a really bad s-w alignment. Something like this: 15D7I7D15I15D15I15D15I15D15I15D
            cigar.addOperation(extend, Cigar::ALIGN);
        }
        else
        {
            const Cigar::Component component = Cigar::decode(cigar.back());
            ISAAC_ASSERT_MSG(Cigar::ALIGN == component.second, "Unexpected operation at the end of cigar " <<
                             Cigar::toString(cigar.begin() + beginOffset, cigar.end()) << " : "  << component.second);
            cigar.updateOperation(cigar.size() - 1, component.first + extend, Cigar::ALIGN);
        }
    }
    return ret;
}

template <unsigned widestGapSize>
void BandedSmithWaterman<widestGapSize>::removeAdjacentIndels(Cigar& cigar, const size_t beginOffset) const
{
//    ISAAC_THREAD_CERR << "BandedSmithWaterman::removeAdjacentIndels " << Cigar::toString(cigar.begin() + beginOffset, cigar.end()) << std::endl;
    const Cigar::iterator begin = cigar.begin() + beginOffset;
    for (Cigar::iterator it = begin + 1; cigar.end() != it;)
    {
        const Cigar::iterator last = it - 1;
        const Cigar::Component lastcomponent = Cigar::decode(*last);
        const Cigar::Component component = Cigar::decode(*it);
        if (Cigar::ALIGN == lastcomponent.second)
        {
            if (Cigar::ALIGN == component.second)
            {
                cigar.updateOperation(std::distance(cigar.begin(), last), lastcomponent.first + component.first, Cigar::ALIGN);
                it = cigar.erase(it);
            }
            else
            {
                ++it;
            }
        }
        else if (Cigar::DELETE == lastcomponent.second)
        {
            if (Cigar::DELETE == component.second)
            {
                cigar.updateOperation(std::distance(cigar.begin(), last), lastcomponent.first + component.first, Cigar::DELETE);
                it = cigar.erase(it);
            }
            else if (Cigar::INSERT == component.second)
            {
                if (component.first < lastcomponent.first)
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), lastcomponent.first - component.first, Cigar::DELETE);
                    cigar.updateOperation(std::distance(cigar.begin(), it), component.first, Cigar::ALIGN);
                }
                else if (lastcomponent.first < component.first)
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), component.first - lastcomponent.first, Cigar::INSERT);
                    cigar.updateOperation(std::distance(cigar.begin(), it), lastcomponent.first, Cigar::ALIGN);
                    if (begin != it - 1)
                    {
                        --it;
                    }
                }
                else
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), component.first, Cigar::ALIGN);
                    it = cigar.erase(it);
                    if (begin != it - 1)
                    {
                        --it;
                    }
                }
            }
            else
            {
                ++it;
            }
        }
        else if (Cigar::INSERT == lastcomponent.second)
        {
            if (Cigar::INSERT == component.second)
            {
                cigar.updateOperation(std::distance(cigar.begin(), last), lastcomponent.first + component.first, Cigar::INSERT);
                it = cigar.erase(it);
            }
            else if (Cigar::DELETE == component.second)
            {
                if (component.first < lastcomponent.first)
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), lastcomponent.first - component.first, Cigar::INSERT);
                    cigar.updateOperation(std::distance(cigar.begin(), it), component.first, Cigar::ALIGN);
                }
                else if (lastcomponent.first < component.first)
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), component.first - lastcomponent.first, Cigar::DELETE);
                    cigar.updateOperation(std::distance(cigar.begin(), it), lastcomponent.first, Cigar::ALIGN);
                    if (begin != it - 1)
                    {
                        --it;
                    }
                }
                else
                {
                    cigar.updateOperation(std::distance(cigar.begin(), last), component.first, Cigar::ALIGN);
                    it = cigar.erase(it);
                    if (begin != it - 1)
                    {
                        --it;
                    }
                }
            }
            else
            {
                ++it;
            }
        }
        else
        {
            ++it;
        }
    }
    //ISAAC_THREAD_CERR << "BandedSmithWaterman::removeAdjacentIndels done " << Cigar::toString(cigar.begin() + beginOffset, cigar.end()) << std::endl;
}

template <unsigned widestGapSize>
unsigned BandedSmithWaterman<widestGapSize>::align(
    const std::vector<char>::const_iterator queryBegin,
    const std::vector<char>::const_iterator queryEnd,
    const reference::Contig::const_iterator databaseBegin,
    const reference::Contig::const_iterator databaseEnd,
    Cigar &cigar) const
{
    assert(databaseEnd > databaseBegin);
    const size_t querySize = std::distance(queryBegin, queryEnd);
    ISAAC_ASSERT_MSG(querySize + WIDEST_GAP_SIZE - 1 == (uint64_t)(databaseEnd - databaseBegin), "q:" << std::string(queryBegin, queryEnd) << " db:" << std::string(databaseBegin, databaseEnd));
    assert(querySize <= size_t(maxReadLength_));
    const size_t originalCigarSize = cigar.size();
    int16_t *t = (int16_t*)T_;

    int16_t GapOpenScore[WIDEST_GAP_SIZE], GapExtendScore[WIDEST_GAP_SIZE];
    for(unsigned i = 0; i < WIDEST_GAP_SIZE; i++) {
        GapOpenScore[i] = gapOpenScore_;
        GapExtendScore[i] = gapExtendScore_;
    }
    // Initialize E, F and G
    int16_t D[WIDEST_GAP_SIZE], E[WIDEST_GAP_SIZE], F[WIDEST_GAP_SIZE], G[WIDEST_GAP_SIZE];
    for(unsigned i = 0; i < WIDEST_GAP_SIZE; i++) {
        E[i] = initialValue_;
        F[i] = 0;
        G[i] = initialValue_;
    }
    G[0] = 0;

    for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
        D[i] = *(databaseBegin + (WIDEST_GAP_SIZE - i - 2));
    }

    // iterate over all bases in the query
    int16_t F1[WIDEST_GAP_SIZE + 1];
    int16_t cmpgtEgMask1[WIDEST_GAP_SIZE + 1], maxEg1[WIDEST_GAP_SIZE + 1];
    F1[0] = initialValue_ + gapExtendScore_;
    maxEg1[0] = initialValue_ + gapOpenScore_;
    cmpgtEgMask1[0] = 0;
    std::vector<char>::const_iterator queryCurrent = queryBegin;
    for (unsigned queryOffset = 0; queryEnd != queryCurrent; ++queryOffset, ++queryCurrent)
    {
        int16_t TE[WIDEST_GAP_SIZE], TF[WIDEST_GAP_SIZE], TG[WIDEST_GAP_SIZE];
        int16_t D1[WIDEST_GAP_SIZE + 1];
        int16_t Q[WIDEST_GAP_SIZE];
        int16_t GA[WIDEST_GAP_SIZE];

        // get F[i-1, j] - extend
        int16_t cmpgtGfMask[WIDEST_GAP_SIZE];

        int16_t *cmpgtEgMaskOff = cmpgtEgMask1 + 1;
        int16_t *maxEgOff = maxEg1 + 1;
        // AV
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            cmpgtEgMaskOff[i] = E[i] > G[i] ? 1 : 0;
        }
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            maxEgOff[i] = G[i] > E[i] ? G[i] : E[i];
        }
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            cmpgtGfMask[i] = F[i] > maxEgOff[i] ? 2 : 0;
        }
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            GA[i] = maxEgOff[i] > F[i] ? maxEgOff[i] : F[i];
        }
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            TG[i] =
                   cmpgtEgMaskOff[i] >
                cmpgtGfMask[i] ? cmpgtEgMaskOff[i] : cmpgtGfMask[i];
        }

        cp(F, F1 + 1);
        int16_t GF1[WIDEST_GAP_SIZE], maxEgSubGapOpen1[WIDEST_GAP_SIZE],
            cmpgtGfMask1[WIDEST_GAP_SIZE];
        // AV
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            GF1[i] = F1[i] - GapExtendScore[i];
            maxEgSubGapOpen1[i] = maxEg1[i] - GapOpenScore[i];
            cmpgtGfMask1[i] = GF1[i] > maxEgSubGapOpen1[i] ? 2 : 0;
            TF[i] =
                cmpgtEgMask1[i] >
                cmpgtGfMask1[i] ? cmpgtEgMask1[i] : cmpgtGfMask1[i];
            F[i] =
                maxEgSubGapOpen1[i] > GF1[i] ? maxEgSubGapOpen1[i] : GF1[i];
        }

        // add the match/mismatch score
        // load the query base in all 8 values of the register
        for(unsigned i = 0; i < WIDEST_GAP_SIZE; i++) {
            Q[i] = *queryCurrent;
        }

        // shift the database by 1 byte to the left and add the new base

        cp(D, D1+1);
        D1[0] = *(databaseBegin + queryOffset + (WIDEST_GAP_SIZE - 1));
        cp(D1, D);

        // compare query and database. 0xff if different (that also the sign bits)
        int16_t B[WIDEST_GAP_SIZE], Match[WIDEST_GAP_SIZE],
        Mismatch[WIDEST_GAP_SIZE], W[WIDEST_GAP_SIZE];

        // lea
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            B[i] = (Q[i] == D[i]) ? 0 : 0xFFFF;
            Match[i] = (~B[i]) & matchScore_;
            Mismatch[i] = B[i] & mismatchScore_;
            W[i] = Match[i] + Mismatch[i];
            G[i] = GA[i] + (W[i] | (B[i] & 0xFF00));
        }

        // E[i,j] = max(G[i, j-1] - open, E[i, j-1] - extend, F[i, j-1] - open)
         int16_t cmpgtFgMask2[WIDEST_GAP_SIZE + 1], maxFg2[WIDEST_GAP_SIZE + 1];
           int16_t *cmpgtFgMaskOff2 = cmpgtFgMask2 + 1;
           int16_t *maxFgOff2 = maxFg2 + 1;
           // AV
           for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
               cmpgtFgMask2[i] = F[i] > G[i] ? 2 : 0;
               maxFg2[i] = F[i] > G[i] ? F[i] : G[i];
               maxFg2[i] -= GapOpenScore[i];
           }

           E[WIDEST_GAP_SIZE - 1] = initialValue_;
        maxFg2[WIDEST_GAP_SIZE] = initialValue_;

        short e = initialValue_;
        short fg = initialValue_;
        for (size_t i = WIDEST_GAP_SIZE; i > 0; i--) {
            short max = fg;
            if (e > fg) {
                max = e;
            }
           E[i - 1] = max;
           fg = maxFg2[i - 1];

           e = max - gapExtendScore_;
        }

        // lea
        int16_t cmpgtFgSueFgMask2[WIDEST_GAP_SIZE];
        cmpgtFgMask2[WIDEST_GAP_SIZE] = initialValue_;
        for (size_t i = 0; i < WIDEST_GAP_SIZE; i++) {
            cmpgtFgSueFgMask2[i] = E[i] > maxFgOff2[i] ? 5 : 0;
            E[i] = E[i] > maxFgOff2[i] ? E[i] : maxFgOff2[i];
            TE[i] =
                (cmpgtFgSueFgMask2[i] >
                 cmpgtFgMaskOff2[i] ? cmpgtFgSueFgMask2[i] :
                 cmpgtFgMaskOff2[i]) & 3;
        }

        TF[0] = 0;

        cp(TG, t);
        cp(TE, t + WIDEST_GAP_SIZE);
        cp(TF, t + WIDEST_GAP_SIZE * 2);
        t += WIDEST_GAP_SIZE * 3;
    }
    // find the max of E, F and G at the end
    short max = G[WIDEST_GAP_SIZE - 1] - 1;

    int ii = querySize - 1;
    int jj = ii;
    unsigned maxType = 0;

    int16_t *TT[] = {G, E, F};
    for (unsigned j = WIDEST_GAP_SIZE; j > 0; j--)
    {
        for (unsigned type = 0; 3 > type; ++type)
        {
            const short value = TT[type][j - 1];
            if (value > max)
            {
                max = value;
                jj = j - 1;
                maxType = type;
            }
        }
    }

    const int jjIncrement[] = {0, 1, -1};
    const int iiIncrement[] = {-1, 0, -1};
    const Cigar::OpCode opCodes[] = {Cigar::ALIGN, Cigar::DELETE, Cigar::INSERT};
    unsigned opLength = 0;
    if (jj > 0)
    {
        cigar.addOperation(jj, Cigar::DELETE);
    }
    while(ii >= 0 && jj >= 0 && jj <= int(WIDEST_GAP_SIZE - 1))
    {
        ++opLength;
        const unsigned nextMaxType = T_[(ii * 3 + maxType)
            * WIDEST_GAP_SIZE * sizeof(int16_t) + (jj * 2)];
        if (nextMaxType != maxType)
        {
            cigar.addOperation(opLength, opCodes[maxType]);
            opLength = 0;
        }
        ii += iiIncrement[maxType];
        jj += jjIncrement[maxType];
        maxType = nextMaxType;
    }
    assert(-1 == ii);
    if (1 != maxType && opLength)
    {
        cigar.addOperation(opLength, opCodes[maxType]);
        opLength = 0;
    }
    if (int(WIDEST_GAP_SIZE - 1) > jj)
    {
        cigar.addOperation(opLength + (WIDEST_GAP_SIZE - 1) - jj, Cigar::DELETE);
        opLength = 0;
    }
    assert(0 == opLength);
    unsigned ret = trimTailIndels(cigar, originalCigarSize);
    std::reverse(cigar.begin() + originalCigarSize, cigar.end());
    trimTailIndels(cigar, originalCigarSize);
    removeAdjacentIndels(cigar, originalCigarSize);
    return ret;
}

template class BandedSmithWaterman<16>;
template class BandedSmithWaterman<32>;
template class BandedSmithWaterman<64>;

} // namespace alignment
} // namespace isaac


/*
 * SimData.cpp
 *
 *  Created on: Feb 15, 2011
 *      Author: ignacio.martin@imdea.org
 *
 * Copyright 2014 IMDEA Materials Institute, Getafe, Madrid, Spain
 *
 * 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.
 */

#include "SimData.h"
#include "MCClient.h"
#include "kernel/Mesh.h"
#include "kernel/Domain.h"
#include "kernel/RateManager.h"
#include "kernel/Constants.h"
#include "lkmc/LatticeParam.h"
#include "lkmc/LatticeAtom.h"
#include "lkmc/LatticeAtomDiamond.h"
#include "lkmc/Lattice.h"
#include "okmc/Particle.h"
#include "okmc/Defect.h"
#include "okmc/EDType.h"
#include "okmc/Interface.h"
#include "okmc/MobileParticle.h"
#include "okmc/Cluster.h"
#include "io/ParameterManager.h"
#include "kernel/StateManager.h"
#include "io/Diagnostic.h"
#include "io/Polynomial.h"
#include "io/FileParameters.h"

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>

#include <iomanip>

using std::vector;
using std::pair;
using std::map;
using std::string;
using std::setw;
using Kernel::Coordinates;
using Kernel::Mesh;

using namespace boost::numeric;

namespace Domains {

SimData::SimData(IO::FileParameters *pFP) : _lastTime(0), _dim(1)
{
	_odelta[0] = pFP->getFloat("MC/Mesh/delta.x");
	_odelta[1] = pFP->getFloat("MC/Mesh/delta.y");
	_odelta[2] = pFP->getFloat("MC/Mesh/delta.z");
}

void SimData::getLKMCInterface(vector<ParticleData>&c, bool bDefective) const
{
	const bool interface = true;
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		LKMC::LatticeSite *first = it->getFirstLS();
		while(first)
		{
			LKMC::LatticeAtom *pLA = dynamic_cast<LKMC::LatticeAtom *>(first);
			if(pLA)
			{
				std::string name = Domains::global()->PM()->getElementName(pLA->getPType());
				if(interface)
				{
					// a/c interface = crystalline atoms having an amorphous NN
					if(pLA->isDefective())
						c.push_back(ParticleData(it->getMaterial(), pLA->getCoordinates(), pLA->getPType(), pLA->getEType(), name));
					else
						for(unsigned j=0; j < pLA->first(); ++j)
							if(pLA->getNeighbor(j) && pLA->getPerformed() && pLA->getNeighbor(j)->getPerformed() != pLA->getPerformed())
							{
								c.push_back(ParticleData(it->getMaterial(), pLA->getCoordinates(), pLA->getPType(), pLA->getEType(), name));
								break ;
							}
				}
				else
					c.push_back(ParticleData(it->getMaterial(), pLA->getCoordinates(), pLA->getPType(), pLA->getEType(), name));
			}
			first = first->getNext();
		}
	}
}

void SimData::getACInterfaceMean(Kernel::Coordinates &min, Kernel::Coordinates &max, Kernel::Coordinates &meanO) const
{
	std::set<LKMC::LatticeAtom *> ac;
	LKMC::LatticeAtom *pLA;
	Kernel::CoordinatesT<double> mean;

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		pLA = static_cast<LKMC::LatticeAtom * >(it->getFirstLS());

		while (pLA) {
			for(unsigned j=0; j < pLA->first(); ++j)
				if(pLA->getNeighbor(j) /*&& amorph.find(pLA->getNeighbor(j)) == amorph.end()*/
						&& !pLA->getNeighbor(j)->getPerformed() && /*!pLA->getNeighbor(j)->getDefective() &&*/ pLA->getPerformed()) {
					double x = pLA->getCoordinates()._x;
					double y = pLA->getCoordinates()._y;
					double z = pLA->getCoordinates()._z;

					if (x >= min._x && x <= max._x && y >= min._y && y <= max._y && z >= min._z && z <= max._z) {
						ac.insert(pLA);
						break ;
					}
				}

			pLA = static_cast<LKMC::LatticeAtom * >(pLA->getNext());
		}
	}

	mean._x = 0.;
	mean._y = 0.;
	mean._z = 0.;

	for (std::set<LKMC::LatticeAtom *>::iterator it = ac.begin(); it != ac.end(); ++it) {
		mean._x += static_cast<double>((*it)->getCoordinates()._x);
		mean._y += static_cast<double>((*it)->getCoordinates()._y);
		mean._z += static_cast<double>((*it)->getCoordinates()._z);
	}
	meanO._x = ac.size() ? mean._x / ac.size() : 0.;
	meanO._y = ac.size() ? mean._y / ac.size() : 0.;
	meanO._z = ac.size() ? mean._z / ac.size() : 0.;
}

float SimData::getACInterfaceCoverage(Kernel::Coordinates &min, Kernel::Coordinates &max) const
{
	std::set<LKMC::LatticeAtom *> ac;
	LKMC::LatticeAtom *pLA;
	Kernel::CoordinatesT<double> mean;

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		pLA = static_cast<LKMC::LatticeAtom * >(it->getFirstLS());

		while (pLA) {
			for(unsigned j=0; j < pLA->first(); ++j)
				if(pLA->getNeighbor(j) && pLA->getNeighbor(j)->getPerformed() && !pLA->getPerformed()) {
					double x = pLA->getCoordinates()._x;
					double y = pLA->getCoordinates()._y;
					double z = pLA->getCoordinates()._z;

					if (x >= min._x && x <= max._x && y >= min._y && y <= max._y && z >= min._z && z <= max._z) {
						ac.insert(pLA);
						break ;
					}
				}
			pLA = static_cast<LKMC::LatticeAtom * >(pLA->getNext());
		}
	}

	unsigned precursor = 0;

	for (std::set<LKMC::LatticeAtom *>::iterator it = ac.begin(); it != ac.end(); ++it)
		if((*it)->getState() == LKMC::LatticeAtom::LS_PRECURSOR)
			precursor++;
	return float(precursor)/float(ac.size());
}

void SimData::getACInterfaceMinMax(Kernel::Coordinates &min, Kernel::Coordinates &max, Kernel::Coordinates &acm, Kernel::Coordinates &acM) const
{
	std::set<LKMC::LatticeAtom *> ac;
	LKMC::LatticeAtom *pLA;
	acm = max;
	acM = min;

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		pLA = static_cast<LKMC::LatticeAtom * >(it->getFirstLS());

		while (pLA)
		{
			for(unsigned j=0; j < pLA->first(); ++j)
				if(pLA->getNeighbor(j) /*&& amorph.find(pLA->getNeighbor(j)) == amorph.end()*/
						&& !pLA->getNeighbor(j)->getPerformed() && /*!pLA->getNeighbor(j)->getDefective() &&*/ pLA->getPerformed())
				{
					double x = pLA->getCoordinates()._x;
					double y = pLA->getCoordinates()._y;
					double z = pLA->getCoordinates()._z;

					if (x >= min._x && x <= max._x && y >= min._y && y <= max._y && z >= min._z && z <= max._z)
					{
						if( x <= acm._x) acm._x = x;
						if( y <= acm._y) acm._y = y;
						if( z <= acm._z) acm._z = z;
						if( x >= acM._x) acM._x = x;
						if( y >= acM._y) acM._y = y;
						if( z >= acM._z) acM._z = z;
						break;
					}
				}
			pLA = static_cast<LKMC::LatticeAtom * >(pLA->getNext());
		}
	}
}

void SimData::getACInterfaceStdev(Kernel::Coordinates &min, Kernel::Coordinates &max, Kernel::Coordinates &stdevO) const
{
	std::set<LKMC::LatticeAtom *> ac;
	LKMC::LatticeAtom *pLA;
	Kernel::CoordinatesT<double> mean, stdev; //to increase precision a little.

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		pLA = static_cast<LKMC::LatticeAtom * >(it->getFirstLS());

		while (pLA) {
		/*	std::set<const LKMC::LatticeAtom *> amorph;
			for(unsigned i=0; i<pLA->first(); ++i) {
				if (pLA->getNeighbor(i) && !pLA->getNeighbor(i)->getPerformed()) {
					// amorphous
					unsigned def = 0;
					for(unsigned j=0; j<pLA->getNeighbor(i)->first(); ++j) {
						if (pLA->getNeighbor(i)->getNeighbor(j) && pLA->getNeighbor(i)->getNeighbor(j)->getDefective())
							++def;
					}

					if (def >= 1) {
						amorph.insert(pLA->getNeighbor(i));
					}
				}
			}*/
			for(unsigned j=0; j < pLA->first(); ++j) {
				if(pLA->getNeighbor(j) /*&& amorph.find(pLA->getNeighbor(j)) == amorph.end() */&& !pLA->getNeighbor(j)->getPerformed()/* && !pLA->getNeighbor(j)->getDefective()*/ && pLA->getPerformed()) {
					double x = pLA->getCoordinates()._x;
					double y = pLA->getCoordinates()._y;
					double z = pLA->getCoordinates()._z;

					if (x >= min._x && x <= max._x && y >= min._y && y <= max._y && z >= min._z && z <= max._z) {
						ac.insert(pLA);
						break ;
					}
				}
			}
			pLA = static_cast<LKMC::LatticeAtom * >(pLA->getNext());
		}
	}

	mean._x = 0.;
	mean._y = 0.;
	mean._z = 0.;

	for (std::set<LKMC::LatticeAtom *>::iterator it = ac.begin(); it != ac.end(); ++it) {
		mean._x += static_cast<double>((*it)->getCoordinates()._x) / static_cast<double>(ac.size());
		mean._y += static_cast<double>((*it)->getCoordinates()._y) / static_cast<double>(ac.size());
		mean._z += static_cast<double>((*it)->getCoordinates()._z) / static_cast<double>(ac.size());
	}

	stdev._x = 0.;
	stdev._y = 0.;
	stdev._z = 0.;
	for (std::set<LKMC::LatticeAtom *>::iterator it = ac.begin(); it != ac.end(); ++it) {
		stdev._x += pow(static_cast<double>((*it)->getCoordinates()._x) - mean._x, 2);
		stdev._y += pow(static_cast<double>((*it)->getCoordinates()._y) - mean._y, 2);
		stdev._z += pow(static_cast<double>((*it)->getCoordinates()._z) - mean._z, 2);
	}

	stdevO._x = ac.size() ? sqrt(stdev._x/ac.size()) : 0.;
	stdevO._y = ac.size() ? sqrt(stdev._y/ac.size()) : 0.;
	stdevO._z = ac.size() ? sqrt(stdev._z/ac.size()) : 0.;
}

void SimData::getOKMCParticles(std::vector<Domains::ParticleData> &data, const vector<string> &defects) const
{
	const unsigned nParticles = Domains::global()->PM()->getNParticles();
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		OKMC::Particle *pPart = it->getFirstPart();
		while(pPart)
		{
			Kernel::Event::E_TYPE     et = pPart->getDefect()->getEType();
			Kernel::P_TYPE              pt = pPart->getPType();
			const Kernel::Coordinates co = pPart->getCoordinates();
			Kernel::M_TYPE            mt = it->getMaterial();
			string name;
			unsigned defectType = 0;
			switch(et)
			{
			case Kernel::Event::CLUSTER:
			{
				OKMC::Cluster *mc = static_cast<OKMC::Cluster *>(pPart->getDefect());
				name = mc->getDomain()->_pClPar->getParams(it->getMaterial(), mc->getEDType())->_name;
				defectType = 2+mc->getEDType();
			}
			break;
			case Kernel::Event::INTERFACE:
				defectType = 1;
				name = Kernel::Event::getEName(et);
			break;

			case Kernel::Event::MOBILEPARTICLE:
			default:
				defectType = 0;
				name = Kernel::Event::getEName(et);
			break;
			}
			if(defects.empty() || std::find(defects.begin(), defects.end(), name) != defects.end())
				data.push_back(Domains::ParticleData(mt, co, pt+defectType*nParticles, et, name, pPart->getDefect()->getIndex(0)));
			pPart = pPart->getNext();
		}

		unsigned howMany = it->getBAtoms();
		Kernel::Domain *pDomain = it->getDomain();
		Kernel::M_TYPE mt = it->getMaterial();
		Kernel::P_TYPE alloyPt = Domains::global()->PM()->getMaterial(mt)._alloy;
		while(howMany--)
		{
			Kernel::Coordinates co;
			pDomain->_pMesh->getRandomPosition(it->getIndex(), co,
				pDomain->_rng_dom.rand(), pDomain->_rng_dom.rand(), pDomain->_rng_dom.rand());
			if(defects.empty() || std::find(defects.begin(), defects.end(), "Alloy") != defects.end())
				data.push_back(Domains::ParticleData(mt, co, alloyPt, Kernel::Event::MOBILEPARTICLE, "Alloy", 0));
		}
	}

}

//include alloy particles
void SimData::collectDefects(map<string, unsigned> maps[Kernel::MAX_MATERIALS]) const
{
	Domains::DefectIterator itEnd = Domains::global()->endDI();
	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		Kernel::ID theMap = it->getID();
		assert(theMap._mt < Domains::global()->PM()->getNMaterials());
		string name = Domains::global()->PM()->getIDName(theMap);
		Kernel::M_TYPE mt = it->getElement()->getMaterial();
		std::stringstream ss;
		switch(it->getEType())
		{
		case Kernel::Event::CLUSTER:
			ss << it->getDomain()->_pClPar->getParams(mt, static_cast<const OKMC::Cluster *>(*it)->getEDType())->_name;
			break;
		case Kernel::Event::INTERFACE:
			continue; //to be treated differently
		default:
			ss << Kernel::Event::getEName(it->getEType());
			break;
		}
		ss << "/" << name;
		if(maps[mt].find(ss.str()) == maps[mt].end())
			maps[mt][ss.str()] = 1;
		else
			maps[mt][ss.str()]++;
	}
	//interfaces only
	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		const OKMC::Interface *pInt = dynamic_cast<const OKMC::Interface *> (*it);
		if(pInt)
		{
			Kernel::ID theMap = pInt->getID();
			string name0 = Domains::global()->PM()->getMaterialName(pInt->getElement(0)->getMaterial());
			string name1 = Domains::global()->PM()->getMaterialName(pInt->getElement(1)->getMaterial());
			std::stringstream ss;
			Kernel::M_TYPE mt;
			if(name0 < name1)
			{
				mt = pInt->getElement(0)->getMaterial();
				ss << name0 << "_" << name1;
			}
			else
			{
				mt = pInt->getElement(1)->getMaterial();
				ss << name1 << "_" << name0;
			}
			for(map<Kernel::P_TYPE, unsigned>::iterator it2 = theMap._pt.begin(); it2 != theMap._pt.end(); ++it2)
			{
				std::stringstream sss;
				sss << ss.str() << "/" << Domains::global()->PM()->getParticleName(theMap._mt, it2->first);
				if(maps[mt].find(sss.str()) == maps[mt].end())
					maps[mt][sss.str()] = it2->second;
				else
					maps[mt][sss.str()] += it2->second;
			}
		}
	}
	//alloy particles
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		unsigned howMany = it->getBAtoms();
		if(howMany)
		{
			Kernel::M_TYPE mt = it->getMaterial();
			Kernel::P_TYPE pt = Domains::global()->PM()->getMaterial(mt)._alloy;
			std::stringstream ss;
			ss << Kernel::Event::getEName(Kernel::Event::MOBILEPARTICLE) << "/" << Domains::global()->PM()->getParticleName(mt, pt);
			if(maps[mt].find(ss.str()) == maps[mt].end())
				maps[mt][ss.str()] = it->getBAtoms();
			else
				maps[mt][ss.str()] += it->getBAtoms();
		}
	}
}

/*
 * orig_mt: Silicon, Iron, etc...
 * pt: I_TYPE, Carbon, etc...
 * defe: <100>, Cluster, etc...
 * theMap: B2I3, I35, etc...
 * defects. false:particle, true:defects
 */
//include alloy particles
unsigned SimData::count(Kernel::M_TYPE orig_mt, const string &partName, const std::string &defe,
		const string &sID, unsigned minSize, bool defects, const std::string &st) const
{
	unsigned counter=0;
	Kernel::P_TYPE names[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		names[mt] = Domains::global()->PM()->getParticleNumber(mt, partName);
	Kernel::ID IDs[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		IDs[mt] = Domains::global()->PM()->getID(mt, sID);
	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		Kernel::M_TYPE mt = it->getElement()->getMaterial();
		Kernel::P_TYPE partType = names[mt];
		vector<const OKMC::Particle *> parts = it->getParticles();
		if(parts.size() < minSize)
			continue;
		for(vector<const OKMC::Particle *>::iterator itPart = parts.begin(); itPart!=parts.end(); ++itPart)
		{
			const OKMC::Particle *pPart = *itPart;
			Kernel::P_TYPE pt = pPart->getPType();
			Kernel::Event::E_TYPE ev = pPart->getDefect()->getEType();
			Kernel::M_TYPE mt = pPart->getElement()->getMaterial();
			OKMC::Cluster *pMC = dynamic_cast<OKMC::Cluster *>(pPart->getDefect());
			OKMC::Interface *pIF = dynamic_cast<OKMC::Interface *>(pPart->getDefect());
			OKMC::MobileParticle *pMP = dynamic_cast<OKMC::MobileParticle *>(pPart->getDefect());
			if(Domains::global()->PM()->getMaterial(mt)._binary == false && (pMC || pIF))
				pt = Domains::global()->PM()->getParticleForCluster(mt, pt, Kernel::POS_0);
			bool isMt = orig_mt == mt;
			bool isPart = pt == partType;
			bool isGenericDefect  = Kernel::Event::getEName(ev) == defe;
			bool isExtendedDefect = pMC && it->getDomain()->_pClPar->getParams(mt, pMC->getEDType())->_name == defe;
			bool isID = IDs[mt] == pPart->getDefect()->getID();
			bool isGenericState = st.empty();
			bool isState = (pMP && st == Domains::global()->PM()->getStateName(mt, pMP->getPType(), pMP->getState()));

			if( (orig_mt == Kernel::MAX_MATERIALS || isMt)   &&
					(partName.empty() || isPart) &&
					(defe.empty()     || isGenericDefect || isExtendedDefect) &&
					(sID.empty()      || isID) &&
					(isState          || isGenericState))
			{
				counter++;
				if(defects)
					break;
			}
		}
	}
	//alloy particles
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		Kernel::M_TYPE mt = it->getMaterial();
		Kernel::P_TYPE partType = names[mt];
		unsigned howMany = it->getBAtoms();
		if(howMany)
		{
			Kernel::M_TYPE mt = it->getMaterial();
			Kernel::P_TYPE pt = Domains::global()->PM()->getMaterial(mt)._alloy;
			if(Domains::global()->PM()->getMaterial(mt)._binary == false)
				pt = Domains::global()->PM()->getParticle(mt, pt, Kernel::POS_0);
			Kernel::Event::E_TYPE ev = Kernel::Event::MOBILEPARTICLE;
			bool isMt = orig_mt == mt;
			bool isPart = pt == partType;
			bool isGenericDefect  = Kernel::Event::getEName(ev) == defe;

			if( (orig_mt == Kernel::MAX_MATERIALS || isMt)   &&
					(partName.empty() || isPart) &&
					(defe.empty()     || isGenericDefect))
			{
				counter+= it->getBAtoms();
				if(defects)
					break;
			}
		}
	}
	return counter;
}

//count number of positions in the simulation
unsigned SimData::countPos(Kernel::M_TYPE orig_mt, const std::string &spos,
		const std::string &defe, const std::string &sID) const
{
	unsigned counter=0;
	Kernel::P_POS positions[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		positions[mt] = Domains::global()->PM()->getPositionNumber(mt, spos);
	Kernel::ID IDs[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
			IDs[mt] = Domains::global()->PM()->getID(mt, sID);

	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		Kernel::M_TYPE mt = it->getElement()->getMaterial();
		Kernel::ID theID = it->getID();
		bool isMt = orig_mt == mt;
		Kernel::Event::E_TYPE ev = (*it)->getEType();
		bool isGenericDefect  = Kernel::Event::getEName(ev) == defe;
		const OKMC::Cluster *pMC = dynamic_cast<const OKMC::Cluster *>(*it);
		bool isExtendedDefect = pMC && it->getDomain()->_pClPar->getParams(mt, pMC->getEDType())->_name == defe;
		bool isID = IDs[mt] == (*it)->getID();
		const OKMC::MobileParticle *pMP = dynamic_cast<const OKMC::MobileParticle *>(*it);

		if(pMP)
		{
			if( (orig_mt == Kernel::MAX_MATERIALS || isMt) &&
		        	 Domains::global()->PM()->getPPos(pMP->getPType()) == positions[mt] &&
		        	 (defe.empty()     || isGenericDefect || isExtendedDefect) &&
		        	 (sID.empty()      || isID)
		        	 )
		        	 	counter++;
		}
		else
			for(map<Kernel::P_POS, unsigned>::iterator posIt=theID._pos.begin(); posIt!=theID._pos.end(); ++posIt)
				if( (orig_mt == Kernel::MAX_MATERIALS || isMt) &&
		        	 posIt->first == positions[mt] &&
		        	 (defe.empty()     || isGenericDefect || isExtendedDefect) &&
		        	 (sID.empty()      || isID)
		        	 )
		        	 	counter += posIt->second;
	}
	return counter;
}

//computes the average radius of the specified defects
double SimData::radius(Kernel::M_TYPE orig_mt, const string &defect, const string &sID, double minRad) const
{
	unsigned count = 0;
	double radius = 0;

	Kernel::ID IDs[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		IDs[mt] = Domains::global()->PM()->getID(mt, sID);

	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		Kernel::M_TYPE mt = it->getElement()->getMaterial();
		const OKMC::Cluster *pMC = dynamic_cast<const OKMC::Cluster *>(*it);
		bool isMt = orig_mt == mt;
		bool isDefect = pMC && it->getDomain()->_pClPar->getParams(mt, pMC->getEDType())->_name == defect;
		bool isID = IDs[mt] == it->getID();
		if( (orig_mt == Kernel::MAX_MATERIALS || isMt)   && pMC &&
			(defect.empty()   || isDefect) &&
			(sID.empty()      || isID))
		{
			double rad = pMC->getRadius();
			if(rad >= minRad)
			{
				count++;
				radius += rad;
			}
		}
	}
	return (count ? radius / count : 0);
}

//static
//given a defect list, it fills it into a vector with the defect types.
void SimData::createDefectList(Kernel::M_TYPE mt, const string &def, vector<unsigned> &defects)
{
	vector<string> texts;
	IO::ParameterManager::getTokens(def, ',', texts);
	IO::ParameterManager * pPM = Domains::global()->PM();
	for(vector<string>::iterator it=texts.begin(); it!=texts.end(); ++it)
	{
		int defect = pPM->getEDType(mt, *it);
		if(defect == -1)
			ERRORMSG("defect name '" << *it << "'not recognized in material" << pPM->getMaterialName(mt));
		defects.push_back(defect);
	}
}

//returns histograms
//for mean values, a tcl script can be used
string SimData::getHistogram(Kernel::M_TYPE orig_mt, const string &orig_defect) const
{
	map<string, unsigned> histogram;

	vector<unsigned> defects;
	createDefectList(orig_mt, orig_defect, defects);
	for(Domains::DefectIterator it = Domains::global()->beginDI(); it!= Domains::global()->endDI(); ++it)
	{
		Kernel::M_TYPE mt = it->getElement()->getMaterial();
		const OKMC::Cluster *pMC = dynamic_cast<const OKMC::Cluster *>(*it);

		if( pMC && orig_mt == mt && std::find(defects.begin(), defects.end(), pMC->getEDType()) != defects.end())
		{
			Kernel::ID id = pMC->getID();
			string name = pMC->name();
			if(histogram.find(name) == histogram.end())
				histogram[name] = 1;
			else
				histogram[name]++;
		}
	}

	std::stringstream ss;
	for(map<string, unsigned>::const_iterator it=histogram.begin(); it!=histogram.end(); ++it)
		ss << it->first << " " << it->second << std::endl;
	return ss.str();
}

// Returns the dose implanted in the material as #FP / #atoms
// Count only the V's
double SimData::getDose(Kernel::M_TYPE mt)
{
    IO::ParameterManager * pPM = Domains::global()->PM();
    map<string, unsigned> theMap = Domains::global()->client()->getCreatedDefects(mt);
    unsigned nFP(0);
    unsigned nAtm(0);
    for(map<string, unsigned>::const_iterator it = theMap.begin(); it != theMap.end(); ++it)
    {
        if(pPM->getDefectType(it->first, mt) == Kernel::Event::MOBILEPARTICLE &&
               it->first == "V")
                nFP += it->second;
        else
        {
            Kernel::ID theID = pPM->getID(mt, it->first);
            for(map<Kernel::P_TYPE, unsigned>::const_iterator pIt = theID._pt.begin();
                    pIt != theID._pt.end(); ++pIt)
                if(pPM->getParticleName(mt, pIt->first) == "V")
                    nFP += it->second * pIt->second;
        }
    }
    for(Domains::MeshElementIterator it = Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
        nAtm += it->getMaterial() == mt ? it->getAtoms() : 0;
    return double(nFP) / double(nAtm);
}

//obtains macroscopic diffusivity
double SimData::getDiffusivity(const string &mate, const string &name, bool bMacro) const
{
	Kernel::M_TYPE mt = Domains::global()->PM()->getMaterialNumber(mate);
	if(mt == Kernel::MAX_MATERIALS)
		ERRORMSG("Material " << mate << " not recognized.");
	Kernel::P_TYPE pt = Domains::global()->PM()->getParticleNumber(mt, name);
	if(pt == Kernel::UNDEFINED_TYPE)
		ERRORMSG("Particle name not recognized");
	if(pt < 2)
		ERRORMSG("I and V particles not allowed");
	if(Domains::global()->PM()->getMaterial(mt)._binary == false && bMacro)
		pt = Domains::global()->PM()->getFamily(pt);
	if(bMacro && pt >= Domains::global()->PM()->getNFamilies())
		ERRORMSG("Species needs to be simple when specifying macroscopic");

	double r2 = 0;
	unsigned howMany = 0;
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if(mt != it->getMaterial())
			continue;
		OKMC::Particle *part = it->getFirstPart();
		while(part)
		{
			if( (bMacro && Domains::global()->PM()->getFamily(part->getPType()) == pt) ||
					pt == part->getPType())
			{
				Kernel::Coordinates r=part->getCoordinates() - part->getOrig();
				r2 += r*r;
				howMany++;
			}
			part = part->getNext();
		}
	}
	double time = Domains::global()->data()->getDeltaTime();
	if(time && howMany)
		return 1./6.*r2*1e-14/howMany/time;
	else
	{
		WARNINGMSG("No time passed or no particles found for diffusion calculation, returning 0");
		return 0;
	}
}

IO::OutDataVectorC<double> SimData::getJumps(const string &name) const
{
	std::vector<std::string> tokens;
	IO::ParameterManager *PM=Domains::global()->PM();
	PM->getTokens(name,'_',tokens);
	Kernel::P_TYPE names[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		names[mt] = Domains::global()->PM()->getParticleNumber(mt, name);
	IO::OutDataVectorC<double> odv3;
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		Kernel::M_TYPE mt = (*it)->getMaterial();
		Kernel::P_TYPE pt = names[mt];
		unsigned state = PM->getStateNumber(mt, pt, tokens[1] );
		Coordinates m,M;
		it->getCorners(m, M);
		odv3.push(3, m, M, double(it->getHops(pt, state)) );
	}
	return collapse(odv3);
}

//takes a 3D information field and "collapses" it into 2D or 1D
//Assumes that the total mesh is a uniform tensor mesh even if there are several domains
IO::OutDataVectorC<double> SimData::collapse(const IO::OutDataVectorC<double> &odv_temp) const
{
	IO::OutDataVectorC<double> odv_c;
	IO::OutDataVectorC<double> odv;

	//filter for min, max
	for(IO::OutDataVectorC<double>::const_iterator it = odv_temp.begin(); it!=odv_temp.end(); ++it)
		if(it->_cm.isIntoEqual(_min, _max) && it->_cM.isIntoEqual(_min, _max))
			odv.push_back(*it);

	if(_dim == 3)
		return odv;

	float eps = 1e-2;
	if(_dim == 2) //collapse z
	{
		//stores all x and all y
		vector<float> linesX = Domains::global()->getDomain(0)->_pMesh->getLines(0);
		vector<float> linesY = Domains::global()->getDomain(0)->_pMesh->getLines(1);

		for(vector<float>::iterator itx = linesX.begin(); itx != linesX.end()-1; ++itx)
			for(vector<float>::iterator ity = linesY.begin(); ity != linesY.end()-1; ++ity)
			{
				double howMany = 0;
				double accumulated = 0;
				for(IO::OutDataVectorC<double>::const_iterator it=odv.begin(); it!=odv.end(); ++it)
					if( it->_cm._x < *itx + eps  && it->_cm._x > *itx - eps &&
						it->_cm._y < *ity + eps  && it->_cm._y > *ity - eps)
					{
						howMany++;
						accumulated += it->_data;
					}
				odv_c.push(2, Coordinates(*itx, *ity, 0), Coordinates(*(itx+1), *(ity+1), 0), howMany? accumulated/howMany:0);
			}
	}
	if(_dim == 1)
	{
		vector<float> linesX = Domains::global()->getDomain(0)->_pMesh->getLines(0);
		for(vector<float>::iterator itx = linesX.begin(); itx != linesX.end()-1; ++itx)
		{
			double howMany = 0;
			double accumulated = 0;
			for(IO::OutDataVectorC<double>::const_iterator it=odv.begin(); it!=odv.end(); ++it)
			{
				if( it->_cm._x < *itx + eps  && it->_cm._x > *itx - eps)
				{
					howMany++;
					accumulated += it->_data;
				}
			}
			odv_c.push(1, Coordinates(*itx, 0, 0), Coordinates(*(itx+1), 0, 0),  howMany? accumulated/howMany:0);
		}
	}
	//dim == 0.
	if(_dim == 0)
	{
		double howMany = 0;
		double accumulated = 0;
		for(IO::OutDataVectorC<double>::const_iterator it=odv.begin(); it!=odv.end(); ++it)
		{
			howMany++;
			accumulated += it->_data;
		}
		odv_c.push(0, Coordinates(), Coordinates(), howMany? accumulated/howMany:0);
	}
	return odv_c;
}

//takes a 3D particle field and "collapses" it into 3,2,1 or 0D creating concentrations.
IO::OutDataVectorC<double> SimData::collapse(const IO::OutDataVectorP<double> &odv_temp) const
{
	IO::OutDataVectorC<double> odv_out;
	IO::OutDataVectorP<double> odv;

	unsigned n[3];
	n[0] = round((_max._x - _min._x) / _odelta[0]);
	n[1] = round((_max._y - _min._y) / _odelta[1]);
	n[2] = round((_max._z - _min._z) / _odelta[2]);
	float delta[3] = {  (_max._x - _min._x)/n[0], (_max._y - _min._y)/n[1], (_max._z - _min._z)/n[2] };
	double factor = 0;

	for(IO::OutDataVectorP<double>::const_iterator it = odv_temp.begin(); it!=odv_temp.end(); ++it)
			if(it->_coords.isIntoEqual(_min, _max))
				odv.push_back(*it);

	if(_dim == 3)
	{
		factor = 1e21/(delta[0]*delta[1]*delta[2]);
		for(unsigned nx=0; nx < n[0]; ++nx)
			for(unsigned ny=0; ny < n[1]; ++ny)
				for(unsigned nz=0; nz < n[2]; ++nz)
					odv_out.push(_dim,
							Coordinates(_min._x + nx    *delta[0], _min._y + ny    *delta[1], _min._z + nz    *delta[2]),
							Coordinates(_min._x + (nx+1)*delta[0], _min._y + (ny+1)*delta[1], _min._z + (nz+1)*delta[2]), 0.);
	}
	if(_dim == 2)
	{
		factor = 1e21/(delta[0]*delta[1]*(_max._z - _min._z));
		for(unsigned nx=0; nx < n[0]; ++nx)
			for(unsigned ny=0; ny < n[1]; ++ny)
				odv_out.push(_dim, Coordinates(_min._x + nx    *delta[0], _min._y + ny    *delta[1], 0),
						           Coordinates(_min._x + (nx+1)*delta[0], _min._y + (ny+1)*delta[1], 0), 0.);
	}
	if(_dim == 1)
	{
		factor = 1e21/(delta[0]*(_max._y - _min._y)*(_max._z - _min._z));
		for(unsigned nx=0; nx < n[0]; ++nx)
			odv_out.push(_dim, Coordinates(_min._x + nx    *delta[0], 0, 0),
							   Coordinates(_min._x + (nx+1)*delta[0], 0, 0), 0.);
	}
	if(_dim == 0)
	{
		factor = 1e21/((_max._x - _min._x)*(_max._y - _min._y)*(_max._z - _min._z));
		odv_out.push(_dim, Coordinates(), Coordinates(), 0.);
	}
	for(IO::OutDataVectorP<double>::const_iterator it=odv.begin(); it!=odv.end(); ++it)
		odv_out[toODVC(delta, n, it->_coords)]._data += it->_data * factor;

	return odv_out;
}

unsigned SimData::toODVC(float delta[3], unsigned n[3], const Coordinates &c) const
{
	unsigned ix = (c._x - _min._x) / delta[0];
	unsigned iy = (c._y - _min._y) / delta[1];
	unsigned iz = (c._z - _min._z) / delta[2];
	switch(_dim)
	{
	case 3:
		return ix*n[2]*n[1] + iy*n[2] + iz;
	case 2:
		return ix*n[1] + iy;
	case 1:
		return ix;
	case 0:
		return 0;
	default:
		ERRORMSG("Incorrect dimension!");
		return 0;
	}
}

IO::OutDataVectorC<double> SimData::getProfileMobile(const string &name, const string &st, const string &mate) const
{

	IO::ParameterManager *PM=Domains::global()->PM();
	double time = Domains::global()->data()->getDeltaTime();
	IO::OutDataVectorC<double> odv;
	bool bAll = st.empty();
	Kernel::M_TYPE mt = Domains::global()->PM()->getMaterialNumber(mate);
	bool bParticleDefined = false;
	Kernel::P_TYPE names[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		names[mt] = Domains::global()->PM()->getParticleNumber(mt, name);
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if (mate.empty()) mt = it->getMaterial();
		if(mt != it->getMaterial())
			continue;
		Kernel::P_TYPE pt = names[it->getMaterial()];
		if(pt == Kernel::UNDEFINED_TYPE || Domains::global()->PM()->isGasLike(mt)) //in gas or similar..
			continue;
		unsigned state = bAll? 0: PM->getStateNumber(mt, pt, st);
		if(state == Kernel::UNDEFINED_STATE)
			continue;
		bParticleDefined = true;
		Coordinates coordm ,coordM;
		it->getCorners(coordm, coordM);
		unsigned endDoWhile = (bAll? PM->getStates(mt, pt): state);
		double temp  = 0;
		do
		{
			double freq = Domains::global()->PM()->getMigrationRate(*it, pt, state);
			unsigned jumps = it->getHops(pt, state);
			temp += jumps? double(jumps)/(time*freq*it->getVolume()*1e-21) : 0.;
			state++;
		} while(state < endDoWhile);
		odv.push(3, coordm, coordM, temp);
	}
	if(bParticleDefined == false)
		ERRORMSG("Particle " << name << " does not seem to be defined in this materials...");
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getParticleProfile(const string &name, const string &def,
		const string &st, const string &mate) const
{
	IO::OutDataVectorP<double> odvp;
	IO::OutDataVectorC<double> odvc;

	Kernel::M_TYPE mt = Domains::global()->PM()->getMaterialNumber(mate);
	Kernel::P_TYPE names[Kernel::MAX_MATERIALS];
	for(unsigned mt=0; mt < Domains::global()->PM()->getNMaterials(); ++mt)
		names[mt] = Domains::global()->PM()->getParticleNumber(mt, name);

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if (!mate.size()) mt = it->getMaterial();
		if(mt != it->getMaterial())
			continue;
		Kernel::P_TYPE pt = names[it->getMaterial()];
		if(pt == Kernel::UNDEFINED_TYPE)
			continue;
		if (!Domains::global()->PM()->isImpurity(mt,pt) && Domains::global()->PM()->isAmorphous((*it)->getMaterial())) //No counting Is nor Vs for amorhpus MEs
			continue;
		Kernel::P_TYPE alloy_pt = Domains::global()->PM()->getMaterial((it->getMaterial()))._alloy;
		if(Domains::global()->PM()->getMaterial(mt)._binary == false)
			alloy_pt = Domains::global()->PM()->getParticle(mt, alloy_pt, Kernel::POS_0);
		if(alloy_pt == pt)
		{
			Coordinates m, M;
			it->getCorners(m, M);
			odvc.push(3, m, M, float(it->getBAtoms()) / float(it->getVolume()) * 1e21);
//			odvc.push(3, m, M, it->getAlloyFraction() * Domains::global()->PM()->getMaterial(it->getMaterial())._densityAlloyCm3);
		}
		else if(name.empty())
		{
			Coordinates m, M;
			it->getCorners(m, M);
			odvc.push(3, m, M, float(it->getAAtoms()) / float(it->getVolume()) * 1e21);
		}
		else
		{
			OKMC::Particle *pPart = it->getFirstPart();
			unsigned stN = Domains::global()->PM()->getStateNumber(it->getMaterial(), pt, st);

			while(pPart)
			{
				Kernel::Event::E_TYPE ev = pPart->getDefect()->getEType();
			    OKMC::Cluster *pMC = dynamic_cast<OKMC::Cluster *>(pPart->getDefect());
			    OKMC::Interface *pIF = dynamic_cast<OKMC::Interface *>(pPart->getDefect());
			    Kernel::P_TYPE thisPt = pPart->getPType();
			    if(Domains::global()->PM()->getMaterial(mt)._binary == false && (pMC || pIF))
					thisPt = Domains::global()->PM()->getParticleForCluster(mt, thisPt, Kernel::POS_0);
			    bool isPart = thisPt == pt;
			    bool isGenericDefect  = Kernel::Event::getEName(ev) == def;
			    bool isExtendedDefect = pMC && it->getDomain()->_pClPar->getParams(mt, pMC->getEDType())->_name == def;
			    bool isSt = pPart->getDefect()->getState() == stN;
			    if(isPart &&
			      (def.empty() || isGenericDefect || isExtendedDefect) &&
			      (st.empty()  || isSt))
			    	odvp.push(3, pPart->getCoordinates(), 1.);
						pPart = pPart->getNext();
			}
		}
	}
	return odvc.size()? collapse(odvc) : collapse(odvp);
}

IO::OutDataVectorC<double> SimData::getAtomProfile(const string &name, const string &mate) const
{
	IO::OutDataVectorC<double> odv;

	Kernel::M_TYPE mt = Domains::global()->PM()->getMaterialNumber(mate);

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if (!mate.size()) mt = it->getMaterial();
		if(mt != it->getMaterial())
			continue;
		Coordinates m, M;
		it->getCorners(m, M);
		if(name == "A.atoms")
			odv.push(3, m, M, it->getAAtoms());
		else
			odv.push(3, m, M, it->getBAtoms());
	}
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getBalance(const string &name, const string &mate) const
{
	IO::OutDataVectorC<double> odv;

	Kernel::M_TYPE mt = Domains::global()->PM()->getMaterialNumber(mate);

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if (!mate.size()) mt = it->getMaterial();
		if(mt != it->getMaterial())
			continue;
		Coordinates m, M;
		it->getCorners(m, M);
		if(name == "A.atoms")
			odv.push(3, m, M, it->_ABalance);
		else
			odv.push(3, m, M, it->_BBalance);
	}
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getLKMCProfile(const string &name) const {
	IO::OutDataVectorP<double> odv;

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		LKMC::LatticeAtom *pLA = static_cast<LKMC::LatticeAtom *>(it->getFirstLS());

		while (pLA) {
			/*if (name == "lkmc.defects" && pLA->getDefective()) {
				odv.push(3, pLA->getCoordinates(), 1.);
			}*/
			if (name == "lkmc.ac" && !pLA->getPerformed()) {
				for (unsigned i = 0; i < pLA->first(); ++i) {
					if (pLA->getNeighbor(i) && pLA->getNeighbor(i)->getPerformed()) {
						odv.push(3, pLA->getCoordinates(), 1.);
						break ;
					}
				}
			}
			pLA = static_cast<LKMC::LatticeAtom *>(pLA->getNext());
		}
	}
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getStrain(const string &name) const
{
	IO::OutDataVectorC<double> odv;
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		Coordinates m, M;
		it->getCorners(m, M);
		if(name == "xx")
			odv.push(3, m, M, it->strain_xx());
		else if(name == "yy")
			odv.push(3, m, M, it->strain_yy());
		else if(name == "zz")
			odv.push(3, m, M, it->strain_zz());
		else if(name == "xy")
			odv.push(3, m, M, it->strain_xy());
		else if(name == "xz")
			odv.push(3, m, M, it->strain_xz());
		else if(name == "yz")
			odv.push(3, m, M, it->strain_yz());
	}
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getStress(const string &name) const
{
	IO::OutDataVectorC<double> odv;
	for(Domains::MeshElementIterator it = Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		Coordinates m, M;
		it->getCorners(m, M);
		if(name == "xx")
			odv.push(3, m, M, it->stress_xx());
		else if(name == "yy")
			odv.push(3, m, M, it->stress_yy());
		else if(name == "zz")
			odv.push(3, m, M, it->stress_zz());
		else if(name == "xy")
			odv.push(3, m, M,  it->stress_xy());
		else if(name == "xz")
			odv.push(3, m, M, it->stress_xz());
		else if(name == "yz")
			odv.push(3, m, M,  it->stress_yz());
	}
	return collapse(odv);
}

IO::OutDataVectorC<double> SimData::getElectrostatic(const string &name) const
{
	IO::OutDataVectorC<double> odv;
	for(Domains::MeshElementIterator it = Domains::global()->beginMEI(); it != Domains::global()->endMEI(); ++it)
	{
		Coordinates m, M;
		it->getCorners(m, M);
		if(name == "potential") {
			odv.push(3, m, M, it->electrostaticPotential());
		}
		else if (name == "electron.density") {
			Kernel::MeshElement *pME = const_cast<Kernel::MeshElement *>(*it);
			std::set<Electrostatics::MeshNode *> s;

			Kernel::Mesh *pMesh = it->getDomain()->_pMesh;
			pMesh->getNodesFromElement(pME, s);

			if (s.empty()) {
				ERRORMSG("SimData::getElectrostatic: cannot find nodes of element " << it->getIndex());
			}

			double dat  = 0;
			for (std::set<Electrostatics::MeshNode *>::const_iterator i = s.begin(); i != s.end(); ++i) {
				dat  += (*i)->getElectronDensity();
			}
			odv.push(3, m, M, dat / static_cast<double>(s.size()));
		}
		else if (name == "hole.density") {
			Kernel::MeshElement *pME = const_cast<Kernel::MeshElement *>(*it);
			std::set<Electrostatics::MeshNode *> s;

			Kernel::Mesh *pMesh = it->getDomain()->_pMesh;
			pMesh->getNodesFromElement(pME, s);

			if (s.empty()) {
				ERRORMSG("SimData::getElectrostatic: cannot find nodes of element " << it->getIndex());
			}

			double dat  = 0;
			for (std::set<Electrostatics::MeshNode *>::const_iterator i = s.begin(); i != s.end(); ++i) {
				dat  += (*i)->getHoleDensity();
			}
			odv.push(3, m, M, dat / static_cast<double>(s.size()));
		}
		else if (name == "bandgap") {
			odv.push(3, m, M, it->bandGap());
		}
		else if (name == "conduction.bandedge") {
			// 0.5 * _Eg(i) - _psi(i)
			odv.push(3, m, M, 0.5 * it->bandGap() - it->electrostaticPotential());
		}
		else if (name == "valence.bandedge") {
			// - 0.5 * _Eg(i) - _psi(i)
			odv.push(3, m, M, - 0.5 * it->bandGap() - it->electrostaticPotential());
		}
	}
	return collapse(odv);
}

std::vector<float> SimData::getMixEnthalpy(const std::string &name) const
{
	std::vector<float> ov(1001);
	IO::FileParameters * pPar =  Domains::global()->getFileParameters();
	IO::Polynomial out = pPar->getPolynomial(name + "/Models/mixing.enthalpy");
	for(unsigned i = 0; i < ov.size(); i++)
	{
		float x = float(i) / 1000.;
		ov[i] = out.getValue(x);
	}
	return ov;
}

string SimData::getCoordination(double r, const Kernel::Coordinates &c, int typ) const
{
	Kernel::Coordinates m(-r-.5, -r-.5, -r-.5), M(r+.5,r+.5,r+.5), rel(0,0,0);
	m += c ;
	M += c;
	std::vector<LKMC::Lattice::LatticeInformation> neis; //type, coordinates
	if(!Domains::global()->client()->isInCell(c))
		ERRORMSG("extract: Coordinate " << c << " is not in this cell");
	Kernel::Domain *pDomain = Domains::global()->getDomain(c);
	Kernel::MeshElement *pEle = pDomain->_pMesh->getElement(pDomain->_pMesh->getIndexFromCoordinates(c));
	Kernel::M_TYPE mt = pEle->getMaterial();
	if(mt == Kernel::MAX_MATERIALS)
		ERRORMSG("Material " << Domains::global()->PM()->getMaterialName(pEle->getMaterial()) << " does not define a material to be transformed into");
	if(!pDomain->_pLat[mt])
		ERRORMSG("Material " << Domains::global()->PM()->getMaterialName(mt) << " does not have a defined lattice!");
	pDomain->_pLat[mt]->fill(m, M, neis, false);
	LOWMSG("Found " << neis.size() << " atoms\n");
	std::multimap<double, int> myMap;  //dist, atom_type
	double minDist = 1e57;
	int atomType = 0;
	for(std::vector<LKMC::Lattice::LatticeInformation>::iterator it=neis.begin(); it<neis.end(); ++it)
	{
		double dist = std::sqrt(
				(it->_coords._x - c._x)*(it->_coords._x - c._x) +
				(it->_coords._y - c._y)*(it->_coords._y - c._y) +
				(it->_coords._z - c._z)*(it->_coords._z - c._z));
		if(typ == -1 || typ == int(it->_type))
			if(dist < minDist)
			{
				minDist = dist;
				atomType = it->_type;
				rel = it->_coords;
			}
	}
	LOWMSG("Taking origin at atom " << rel << " of type " << atomType);
	LOWMSG("Dist   Typ H0  H1  H2");
	for(std::vector<LKMC::Lattice::LatticeInformation>::iterator it=neis.begin(); it<neis.end(); ++it)
	{
		double dist = std::sqrt(
				(it->_coords._x - rel._x)*(it->_coords._x - rel._x) +
				(it->_coords._y - rel._y)*(it->_coords._y - rel._y) +
				(it->_coords._z - rel._z)*(it->_coords._z - rel._z));
		if(dist > r)
			continue;
		myMap.insert(std::pair<double, int>(dist, it->_type));  //dist atom_type
	}
	int howMany[3] = { 0, 0, 0 };
	std::stringstream ss;
	for(std::multimap<double, int>::iterator it=myMap.begin(); it!=myMap.end(); ++it)
	{
		howMany[it->second]++;
		ss << std::right << setw(6) << std::setprecision(3) << it->first << " " << it->second << " " << setw(3) <<
				howMany[0] << " " << setw(3) << howMany[1] << " " << setw(3) << howMany[2] << std::endl;
	}
	return ss.str();
}

unsigned SimData::countDisplaced(const std::string &mate, const bool bAmorph, const float threshold) const
{
	IO::ParameterManager *PM = Domains::global()->PM();
	unsigned counter = 0, meCounter = 0;
	Kernel::M_TYPE mt = PM->getMaterialNumber(mate);
	string name = mate + "/Models/atomic.density";
	double amorph = Domains::global()->getFileParameters()->getFloat(name);
	float thrs = threshold > 0 ? threshold : 1.e24;

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		if(PM->getMaterial(it->getMaterial())._basicMaterial == mt)
		{
			if(PM->isAmorphous(it->getMaterial()) )
				meCounter += unsigned(amorph/1.e21*it->getVolume());
			else
			{
				OKMC::Particle *pPart = it->getFirstPart();
				while(pPart) {

					Kernel::P_TYPE pt = pPart->getPType();
					if( PM->getFamily(pt) == PM->getMaterial(mt)._pt[0] || PM->getFamily(pt) == PM->getMaterial(mt)._pt[1] )
						meCounter++;
					pPart = pPart->getNext();
				}
			}
		}
		if (!bAmorph && double(meCounter)/it->getVolume()*1.e21 >= thrs)
			counter += unsigned(amorph/1.e21*it->getVolume());
		else
			counter += meCounter;
		meCounter = 0;
	}
	return counter;
}

IO::OutDataVectorC<double> SimData::amorphousFraction() const
{
	IO::ParameterManager *PM = Domains::global()->PM();
	IO::OutDataVectorC<double> odvc;
	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
		Coordinates m, M;
		it->getCorners(m, M);
		if( PM->isAmorphous(it->getMaterial()) )
			odvc.push(3,m,M,1.);
		else
			odvc.push(3,m,M,0.);
	}

	return collapse(odvc);
}

IO::OutDataVectorC<double> SimData::getDamageProfile() const
{
	IO::OutDataVectorC<double> odvc;
	IO::ParameterManager *PM = Domains::global()->PM();

	for(Domains::MeshElementIterator it=Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
	{
			Coordinates m, M;
			it->getCorners(m, M);
			if(Domains::global()->PM()->isAmorphous((*it)->getMaterial()))
				odvc.push(3,m,M,PM->getMaterial(PM->getMaterial(it->getMaterial())._basicMaterial)._amorphThreshold);
			else
			{
				OKMC::Particle *pPart = it->getFirstPart();
				unsigned nParts = 0;
				while(pPart) {
					Kernel::P_TYPE pt = pPart->getPType();
					if( !Domains::global()->PM()->isImpurity(it->getMaterial(),pt) )
						nParts++;
					pPart = pPart->getNext();
				}
				odvc.push(3,m,M,double(nParts)/it->getVolume()*1.e21);
			}
	}

	return collapse(odvc);
}

double SimData::getDeltaTime() const
{
	return Domains::global()->getTime() - _lastTime;
}

void SimData::reset()
{
	_lastTime = Domains::global()->getTime();
	// now reset the hops
	for(Domains::MeshElementIterator it = Domains::global()->beginMEI(); it!=Domains::global()->endMEI(); ++it)
		it.modify()->resetHops();
	for(Domains::ParticleIterator it = Domains::global()->beginPI(); it!=Domains::global()->endPI(); ++it)
		it.modify()->setOrig(it->getCoordinates());
}

//returns an y,z map with the free interface position.
string SimData::getFuzz() const
{
	std::stringstream ss;
	std::map<std::pair<float, float>, float> interface;

	Kernel::Coordinates cell, Cell;
	for(Domains::MeshElementIterator mi = Domains::global()->beginMEI(); mi != Domains::global()->endMEI(); ++mi)
	{
		mi->getCorners(cell, Cell);
		std::pair<float, float> yz(cell._y, cell._z);
		if( (interface.find(yz) == interface.end() || interface[yz] > cell._x) && mi->getInterfaces().size())
			interface[yz] = cell._x;
	}

	for(std::map<std::pair<float, float>, float>::iterator it=interface.begin(); it!=interface.end(); ++it)
		ss << it->first.first << " " << it->first.second << " " << it->second << std::endl;
	return ss.str();
}

}


// Copyright (C) 2018 Thejaka Amila Kanewala, Marcin Zalewski, Andrew Lumsdaine.

// Boost Software License - Version 1.0 - August 17th, 2003

// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:

// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//  Authors: Thejaka Kanewala
//           Andrew Lumsdaine

//======== Maximal Independent Set Algortihms================//
// This implements two algorithms: 1. FIX
// MIS algorithm 2. FIX-PQ algorithm with thread level
// ordering (use MIS_PRIORITY preprocessor macro)
//===========================================================//

#ifndef BOOST_GRAPH_MIS
#define BOOST_GRAPH_MIS

#ifndef BOOST_GRAPH_USE_MPI
#error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included"
#endif

#include <am++/counter_coalesced_message_type.hpp>
#include <am++/detail/thread_support.hpp>

#include <boost/parallel/append_buffer.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/graph/parallel/algorithm.hpp> // for all_reduce
#include <boost/graph/parallel/iteration_macros.hpp> // for all_reduce
#include <boost/graph/parallel/thread_support.hpp> // for compare_and_swap
#include <algorithm> // for std::min, std::max
#include <boost/format.hpp>
#include <iostream>
#include <atomic>
#include "boost/tuple/tuple.hpp"
#include "thread_pq_def.hpp"
#include <boost/graph/distributed/owner_defs.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/discrete_distribution.hpp>

//for profiling
#ifdef CRAYPAT
#include <pat_api.h>
#endif

#define MIS_UNFIX 0
#define MIS_FIX1 1
#define MIS_FIX0 2

typedef int state_t;

namespace boost { namespace graph { namespace distributed {

template<typename Graph, typename MISMap,
	 typename IdDistribution,
	 typename PriorityQueueGenerator = thread_priority_queue_gen,
         typename MessageGenerator =
           amplusplus::simple_generator<amplusplus::counter_coalesced_message_type_gen> >
class maximal_independent_set {

  typedef maximal_independent_set<Graph, MISMap, IdDistribution,
				  PriorityQueueGenerator, MessageGenerator>
    self_type;

  typedef typename boost::property_map<Graph, vertex_owner_t>::const_type OwnerMap;

  typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
  typedef typename graph_traits<Graph>::degree_size_type Degree;

  typedef append_buffer<Vertex, 10u> InitialBuffer;

  typedef unsigned long distance_t;

  // < Target, < < Source, Source State >, distance from min neighbor> >
  typedef std::pair<Vertex, std::pair<Vertex, std::pair<state_t, distance_t> > > work_item_t;

  static Vertex targetv(work_item_t wi) {
    return wi.first;
  }

  static Vertex sourcev(work_item_t wi) {
    return wi.second.first;
  }

  static state_t statev(work_item_t wi) {
    return wi.second.second.first;
  }

  static distance_t distancev(work_item_t wi) {
    return wi.second.second.second;
  }

  typedef typename std::map<Vertex, int > NeighborCountMap;

  struct default_comparer {
    bool operator()(const work_item_t& wi1, const work_item_t& wi2) {
      state_t s1 = statev(wi1);
      state_t s2 = statev(wi2);

      if (s1 > s2)
	return true;
#ifdef MIS_DISTANCE_ORDERING
      else if (s1 == s2) { // MIS_FIX1 vertices should be processed first
	distance_t d1 = distancev(wi1);
	distance_t d2 = distancev(wi2);

	if (d1 > d2) {
	  return true;
	} else if (d1 == d2) {
	  Vertex v1 = targetv(wi1);
	  Vertex v2 = targetv(wi2);

	  if (v1 > v2)
	    return true;
	  else
	    return false;

	} else
	  return false;
      } else
	return false;
#else // distance ordering not defined
      else
	return false;
#endif

    }

    /*#ifdef 0
    bool operatorinverse()(const work_item_t& wi1, const work_item_t& wi2) const {
      state_t s1 = statev(wi1);
      state_t s2 = statev(wi2);

      if (s1 < s2)
	return true;
      else if (s1 == s2) {
	if (s1 == 0) {
	  distance_t d1 = distancev(wi1);
	  distance_t d2 = distancev(wi2);

	  if (d1 < d2) { // MIS_FIX1 vertices should be processed first
	    return true;
	  } else if (d1 == d2) {
	    Vertex v1 = targetv(wi1);
	    Vertex v2 = targetv(wi2);

	    if (v1 < v2)
	      return true;
	    else
	      return false;

	  } else
	    return false;
	} else {
	  Vertex v1 = targetv(wi1);
	  Vertex v2 = targetv(wi2);

	  if (v1 < v2)
	    return true;
	  else
	    return false;
	}
      } else
	return false;

    }
    #endif*/
  };

  typedef typename PriorityQueueGenerator::template queue<work_item_t,
							  default_comparer>::type PriorityQueue_t;
  struct processing_function;

  struct minimum_pair_first
  {
    template<typename T>
    const T& operator()(const T& x, const T& y) const { return x.first < y.first ? x : y; }

    template<typename F>
    struct result {
      typedef typename boost::function_traits<F>::arg1_type type;
    };
  };

  typedef typename MessageGenerator::template call_result<work_item_t,
							  processing_function,
							  owner_from_pair<OwnerMap, work_item_t>,
							  amplusplus::idempotent_combination_t<minimum_pair_first > >::type RelaxMessage;

public:
  maximal_independent_set(Graph& g,
			  MISMap& mismap,
			  amplusplus::transport &t,
			  const IdDistribution& idd,
			  int freq,
			  int offset,
			  MessageGenerator message_gen =
			  MessageGenerator(amplusplus::counter_coalesced_message_type_gen(1 << 12)))
    : dummy_first_member_for_init_order((amplusplus::register_mpi_datatype<work_item_t>(), 0)),
      g(g), transport(t),
      id_distribution(idd),
      mis(mismap),
      owner(get(vertex_owner, g)),
      core_offset(offset),
    relax_msg(message_gen, transport, owner_from_pair<OwnerMap, work_item_t>(owner),
	      amplusplus::idempotent_combination(minimum_pair_first())),
      flushFrequency(freq), pq(t.get_nthreads()), counter(0)
  {
    initialize();
  }

  void operator() (int tid) {

    run(tid);

  }

  void run(int tid = 0);
  time_type get_start_time() {
    return start_time;
  }

  void add_to_pq(const work_item_t& data, int tid) {
    pq.push(data, tid);
  }

#ifdef MIS_STATS
  void print_stats() {
    unsigned long skipped = 0;
    unsigned long called = 0;

    std::vector<unsigned long>::iterator ite = skipped_handlers.begin();
    for (; ite != skipped_handlers.end(); ++ite) {
      skipped += (*ite);
    }

    ite = called_handlers.begin();
    for (; ite != called_handlers.end(); ++ite) {
      called += (*ite);
    }

    std::cout << "Skiped : " << skipped << ", Called : " << called
	      << std::endl;
  }
#endif

#ifdef MIS_PRIORITY
  void print_pq_sizes() {
#ifdef MIS_STATS
    int i = 0;
    std::vector<size_t>::iterator ite = pq_sizes.begin();
    for(; ite != pq_sizes.end(); ++ite) {
      std::cout << "thread : " << i << ", pq size : " << (*ite)
		<< std::endl;
      ++i;
    }
#endif
  }
#endif

private:
  void initialize();
  void process(const work_item_t& data, int tid);
  void handle_queue(const int tid, amplusplus::transport::end_epoch_request& request);
  void handle_fix0_wi(const work_item_t& data, int tid, int& flushcnt);

  template<typename SizeType>
  inline SizeType logical_id(SizeType k) {
    return id_distribution(k);
  }

  work_item_t construct_wi(Vertex d,
			   Vertex s,
			   state_t state,
			   distance_t distance) {
    // typedef std::pair<Vertex, std::pair<Vertex, std::pair<state_t, distance_t> > > work_item_t;
    work_item_t wi(d, std::make_pair(s, std::make_pair(state, distance)));
    return wi;
  }

  const int dummy_first_member_for_init_order;
  const Graph& g;
  amplusplus::transport& transport;
  const IdDistribution& id_distribution;
  MISMap& mis;
  OwnerMap owner;
  shared_ptr<amplusplus::detail::barrier> t_bar;
  RelaxMessage relax_msg;
  time_type start_time;
  int flushFrequency;
  PriorityQueue_t pq;
  std::atomic_ullong counter;
  NeighborCountMap lower_neighbors;
  NeighborCountMap lower_fixed_neighbors;
  shared_ptr<InitialBuffer> buffer;
  int core_offset;
#ifdef MIS_STATS
  std::vector<unsigned long> skipped_handlers;
  std::vector<unsigned long> called_handlers;
#endif
#ifdef MIS_PRIORITY
#ifdef MIS_STATS
  std::vector<size_t> pq_sizes;
#endif
#endif

};

#define MIS_PARMS                                   \
      typename Graph, typename MISMap, typename IdDistribution, typename PriorityQueueGenerator, typename MessageGenerator

#define MIS_TYPE                                    \
      maximal_independent_set<Graph, MISMap, IdDistribution, PriorityQueueGenerator, MessageGenerator>

template<MIS_PARMS>
void
MIS_TYPE::initialize() {

#ifdef MIS_PRIORITY
  std::cout << "Running with MIS priority ....." << std::endl;
#ifdef MIS_STATS
  pq_sizes.resize(transport.get_nthreads(), 0);
#endif
#else
  std::cout << "Running with *out* MIS priority ....." << std::endl;
#endif

  relax_msg.set_handler(processing_function(*this));

#ifdef MIS_STATS
  skipped_handlers.resize(transport.get_nthreads(), 0);
  called_handlers.resize(transport.get_nthreads(), 0);
#endif

  //
  shared_ptr<InitialBuffer> p(new InitialBuffer);
  buffer.swap(p);

  BGL_FORALL_VERTICES_T(u, g, Graph) {
    mis[u] = MIS_UNFIX;
    // lower_neighbors are not initialized
    std::set<Vertex> locadjacencies;
    int count = 0;
    BGL_FORALL_OUTEDGES_T(u, e, g, Graph) {
      Vertex v = target(e, g);
      if (logical_id(v) < logical_id(u)) {
	if (locadjacencies.insert(v).second) { //to avoid parallel edges
	  ++count;
	}
      }
    }

    if (count == 0) {
      buffer->push_back(u);
    }

    lower_neighbors.insert(std::make_pair(u, count));
    lower_fixed_neighbors.insert(std::make_pair(u, 0));
  }

}

template<MIS_PARMS>
void
MIS_TYPE::handle_fix0_wi(const work_item_t& data, int tid, int& flushcnt) {
  Vertex dest_vertex = targetv(data);
  Vertex source_vertex = sourcev(data);
  state_t source_state = statev(data);
  distance_t dist = distancev(data);

#ifdef MIS_PRIORITY
  if (mis[dest_vertex] != MIS_UNFIX) {
#ifdef PRINT_DEBUG
    assert(mis[dest_vertex] == MIS_FIX1 ||
	   mis[dest_vertex] == MIS_FIX0);

    if (mis[dest_vertex] == MIS_FIX1) {
      if (logical_id(source_vertex) < logical_id(dest_vertex)) {
	std::cout << "To dest_vertex : " << dest_vertex << " to be fixed we must have checked all its lower neighbors ("
		  << source_vertex << ")" <<
	  "lfn=" << lower_fixed_neighbors[dest_vertex] << " ln=" << lower_neighbors[dest_vertex] <<
	  std::endl;
	assert(false);
      }
    }
#endif

#ifdef MIS_STATS
    ++skipped_handlers[tid];
#endif
    return;
  }
#endif

  // case 3:
  // Source vertex is not in mis and fixed =>
  // If source vertex is less than destination vertex, then add
  // 1 to lower_fixed_neighbors
  // Also check, all neighbors less than current vertex are in
  // lower_fixed_neighbors, if so promote dest_vertex to be in MIS and fix (MIS_FIX1).
  // Inform neigbors about state changes.
  assert(source_state == MIS_FIX0);

#ifdef MIS_STATS
  ++called_handlers[tid];
#endif

  if (logical_id(source_vertex) < logical_id(dest_vertex)) {

#ifdef PRINT_DEBUG
    assert(lower_neighbors[dest_vertex] > 0);
    if (lower_fixed_neighbors[dest_vertex] >= lower_neighbors[dest_vertex]) {
      std::cout << "lfn=" << lower_fixed_neighbors[dest_vertex] << " ln=" << lower_neighbors[dest_vertex]
		<< std::endl;
    }
#endif

    // if all lower neigbors are fixed dest vertex must also be fixed
    assert(lower_fixed_neighbors[dest_vertex] < lower_neighbors[dest_vertex]);

    if (__atomic_add_fetch(&lower_fixed_neighbors[dest_vertex], 1, __ATOMIC_SEQ_CST) == lower_neighbors[dest_vertex]) {
      // all the lower neigbors are fixed
      assert(lower_fixed_neighbors[dest_vertex] == lower_neighbors[dest_vertex]);
      mis[dest_vertex] = MIS_FIX1;
      // vertex is fixed. We do not need to maintain lower_fixed_neighbors
      // for this vertex any more.
      // source not in mis -- just inform every neighbor the current state
      std::set<Vertex> adjacencies;
      BGL_FORALL_OUTEDGES_T(dest_vertex, e, g, Graph) {
	Vertex u = target(e, g);
	if (logical_id(u) > logical_id(dest_vertex)) { // we know that all lower neighbors are fixed, therefore we only need to send messages to higher neighbors
	  if (adjacencies.insert(u).second) {
#ifdef MIS_PRIORITY
	    //	    if (fix0vertices[tid].insert(u).second) {
	      ++flushcnt;
#endif
	      work_item_t wi = construct_wi(u, dest_vertex, mis[dest_vertex],
					    (dist + 1));
#ifndef MIS_PRIORITY
	      //	      transport.increase_activity_count(1);
#endif
	      relax_msg.send(wi);
#ifdef MIS_PRIORITY
	      // }
#endif
	  }
	}
      }
    }
  }
}

template<MIS_PARMS>
void
MIS_TYPE::process(const work_item_t& data, int tid) {
  //   typedef std::pair<Vertex, std::pair<Vertex, state_t > > work_item_t;

  /*#ifdef CRAYPAT
  if (PAT_region_begin ( 2, "misprocess" ) == PAT_API_FAIL) {
    std::cout << "PAT begin failed ! " << std::endl;
    assert(false);
  }
  #endif*/

  Vertex dest_vertex = targetv(data);
  Vertex source_vertex = sourcev(data);
  state_t source_state = statev(data);
  distance_t dist = distancev(data);

#ifdef PRINT_DEBUG
  std::cout << "dv : " << dest_vertex << " sv : " << source_vertex << " s_state : " << source_state
	    << " d_state :" << mis[dest_vertex] << " distance : " << dist
	    << std::endl;
#endif

  // case 1:
  // is destination vertex fixed ? then just ignore
  if (mis[dest_vertex] != MIS_UNFIX) {
    assert(mis[dest_vertex] == MIS_FIX1 ||
	   mis[dest_vertex] == MIS_FIX0);
#ifndef MIS_PRIORITY
    //    transport.decrease_activity_count(1);
#endif

    /*#ifdef CRAYPAT
    if (PAT_region_end(2) == PAT_API_FAIL) {
     std::cout << "PAT end failed ! " << std::endl;
     assert(false);
    }
    #endif*/

#ifdef MIS_STATS
    ++skipped_handlers[tid];
#endif
    return;
  }

  // case 2:
  // Source vertex is in mis and fixed =>
  // Destination vertex state must be transferred to MIS_FIX0.
  if (source_state == MIS_FIX1) {
#ifdef MIS_STATS
    ++called_handlers[tid];
#endif
    //mis[dest_vertex] = MIS_FIX0;
    // source in mis, therefore dest must be out of mis
    int expected = MIS_UNFIX;
    if (__atomic_compare_exchange_n(&mis[dest_vertex], &expected, MIS_FIX0, false /*no weak*/,
				    __ATOMIC_SEQ_CST, __ATOMIC_RELAXED)) {

      assert(mis[dest_vertex] == MIS_FIX0);
      // inform all neighbors
      std::set<Vertex> adjacencies;
      BGL_FORALL_OUTEDGES_T(dest_vertex, e, g, Graph) {
	Vertex u = target(e, g);
	if (logical_id(u) > logical_id(dest_vertex)) {
	  if (adjacencies.insert(u).second) {
	    work_item_t wi = construct_wi(u, dest_vertex, mis[dest_vertex],
					  (dist + 1));

#ifdef MIS_PRIORITY
	    relax_msg.send(wi);
#else
	    //	    transport.increase_activity_count(1);
	    relax_msg.send(wi);
#endif
	  }
	}
      }

    }
  } else {
#ifdef MIS_PRIORITY

    if (pq.empty(tid)) {
      transport.increase_activity_count(1);
    }
    pq.put(data, tid);

#else
    int ignore = 0;
    handle_fix0_wi(data, tid, ignore);
#endif
  } // end of else

#ifndef MIS_PRIORITY
  // done processing the work item
  //  transport.decrease_activity_count(1);
#endif

  /*#ifdef CRAYPAT
  if (PAT_region_end(2) == PAT_API_FAIL) {
    std::cout << "PAT end failed ! " << std::endl;
    assert(false);
  }
  #endif*/

}

template<MIS_PARMS>
void
MIS_TYPE::run(int tid) {
  AMPLUSPLUS_WITH_THREAD_ID(tid) {

#ifdef CRAYPAT
      if (PAT_region_begin ( 1, "misrun" ) == PAT_API_FAIL) {
	std::cout << "PAT begin failed ! " << std::endl;
	assert(false);
      }
#endif

    int nthreads = transport.get_nthreads();
    if (0 == tid) {
      // Set the number of threads to the barrier
      t_bar.reset(new amplusplus::detail::barrier(nthreads));
    }

    { amplusplus::scoped_epoch epoch(transport); }

    // Now above if branch needs to be executed to every thread
    // Therefore wait till every thread comes to this point
    t_bar->wait();

    // if two processes are running on the same node, core_offset
    // is important to achieve thread affinity
    if (pin(tid+core_offset) != 0) {
      std::cerr << "[ERROR] Unable to pin current thread to "
		<< "core : " << tid << std::endl;
      assert(false);
    }

    // wait till all threads are pinned
    t_bar->wait();
    { amplusplus::scoped_epoch epoch(transport); }

    validate_thread_core_relation();

    // should come before begin epoch
    start_time = get_time();
    // Start the algorithm
    transport.begin_epoch();

    typename InitialBuffer::size_type buff_size;
    buff_size = buffer->size();
    for (typename InitialBuffer::size_type i = tid ;
	 i < buff_size ; i+= nthreads) {
      Vertex u = (*buffer)[i];
      mis[u] = MIS_FIX1;
      std::set<Vertex> adjacencies;
      BGL_FORALL_OUTEDGES_T(u, e, g, Graph) {
	Vertex v = target(e, g);
	if (u != v) { // ignore self loops
	  if (adjacencies.insert(v).second) {
	    work_item_t wi = construct_wi(v, u, mis[u], 0);
	    relax_msg.send(wi);
	  }
	}
      }

    }
#ifdef PRINT_DEBUG
    // for information, can remove
    if (tid == 0)
      std::cout << "Number of minimum_neighbor_vertices : " << counter << std::endl;
#endif

    amplusplus::transport::end_epoch_request request = transport.i_end_epoch();
    // if we have work process queues. queue is populated in the relax_msg
    handle_queue(tid, request);

#ifdef CRAYPAT
    if (PAT_region_end(1) == PAT_API_FAIL) {
      std::cout << "PAT end failed ! " << std::endl;
      assert(false);
    }
#endif

  }
}

template<MIS_PARMS>
void
MIS_TYPE::handle_queue(const int tid, amplusplus::transport::end_epoch_request& request) {
  int doFlushCounter = 0;
  while(true) {
#ifdef MIS_PRIORITY
#ifdef MIS_STATS
    if (pq.size(tid) > pq_sizes[tid]) {
      pq_sizes[tid] = pq.size(tid);
    }
#endif

    work_item_t wi;
    while(pq.pop(wi, tid)) {
      // to make sure pq is ordering properly TODO remove once verified
#ifdef PRINT_DEBUG
      std::cout << "target vertex : " << targetv(wi) << " source : " << sourcev(wi) << std::endl;
#endif

      bool e = pq.empty(tid);
      handle_fix0_wi(wi, tid, doFlushCounter);

      if (e)
	transport.decrease_activity_count(1);

      if(doFlushCounter == flushFrequency) {
	doFlushCounter = 0;
	if (request.test()) {
	  assert(pq.empty(tid));
	  break;
	}
      }
    }

    if (request.test())
      break;
#else
    if (request.test())
      break;
#endif
  }
}

template<MIS_PARMS>
struct MIS_TYPE::
processing_function {

  processing_function() : self(NULL) {}
  processing_function(maximal_independent_set& self) : self(&self) {}

  void operator() (const work_item_t& data) const {
    int tid = amplusplus::detail::get_thread_id();
#ifdef PRINT_DEBUG
    std::cout << "Handler called in tid : " << tid << std::endl;
#endif
    self->process(data, tid);
  }

protected:
  maximal_independent_set* self;
};

}}}
#endif


/*
Copyright (c) 2020 Salah Eddine Bouterfif

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <fstream>
#include <sstream>
#include <ciso646>
#include <iomanip>

#include "Randomizer.h"
#include "thirdparty/cxxopts/cxxopts.hpp"

#ifndef __has_include
static_assert(false, "__has_include not supported");
#else
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#elif __has_include(<boost/filesystem.hpp>)
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#endif
#endif

// minmax
#ifndef MIN
#define MIN(a, b) (((a) > (b)) ? (b) : (a))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

std::pair<std::string, std::string> generateInstance(int i, int nb_jobs, int nb_machines, int &k, int &nb_perturbations, double &fixed_percentage, long &seed, int &lower_bound, float &workload)
{

  // calculate the task index in ptime_arr
  auto calTaskIdx = [&](int row_idx, int col_idx)
  {
    return row_idx * nb_machines + col_idx;
  };

  std::vector<int> ptime_arr(nb_jobs * nb_machines);
  Randomizer random(seed);
  seed = random.seed();
  workload = 0.0;
  lower_bound = 0;
  bool is_random_hard = false;

  if (k == 0 or nb_perturbations == 0 or fixed_percentage == 0)
    is_random_hard = true;

  if (is_random_hard)
  {
    // use these random parameters if parameters are 0
    k = static_cast<int>(random(nb_jobs * nb_machines, nb_jobs * nb_machines * 100));
    nb_perturbations = static_cast<int>(random(nb_jobs * nb_jobs, nb_jobs * nb_jobs * nb_machines));
    fixed_percentage = (double)random(RAND_MAX) / (RAND_MAX);
  }

  // square problems
  if (nb_jobs == nb_machines /*or nb_jobs < nb_machines*/)
  {
    for (int i = 0; i < nb_jobs; ++i)
    {
      // sum of each row = k
      for (int j = 0; j < nb_machines; ++j)
      {
        ptime_arr[calTaskIdx(i, j)] = int(floor(k / nb_machines));
      }
      ptime_arr[calTaskIdx(i, i)] += k % (nb_machines);
    }
  }
  // for rectangle problems, make sur that sum of all row is equal to k
  // devide the rec into a square and add k mod m to a square sub matrix
  // at a time ensuring all sum of row elem is equal to k
  else
  {
    int inc = 0;
    int min = MIN(nb_jobs, nb_machines);
    // populate ptime_arr and apply the mod on the main diagonal
    for (int i = 0; i < nb_jobs; ++i)
    {
      if (inc == min)
        inc = 0;
      // sum of each row = k
      for (int j = 0; j < nb_machines; ++j)
      {
        ptime_arr[calTaskIdx(i, j)] = int(floor(k / nb_machines));
        if (i >= min and j == inc)
          ptime_arr[calTaskIdx(i, j)] += k % (nb_machines);
      }
      if (i < min)
        ptime_arr[calTaskIdx(i, i)] += k % (nb_machines);
      inc++;
    }
  }

  std::stringstream ss_instance;
  std::stringstream ss_name;
  ss_name.str("");
  ss_instance.str("");
  // perturbations to to ptime_arr
  for (int i = 0; i < nb_perturbations; ++i)
  {
    // task 1
    int idx1 = 0;
    int idx2 = 0;
    // task 2
    int idx3 = 0;
    int idx4 = 0;

    // select 2 different taks randomely
    while (idx1 == idx3 or idx2 == idx4)
    {
      idx1 = static_cast<int>(random(0, nb_jobs - 1));
      idx2 = static_cast<int>(random(0, nb_machines - 1));
      idx3 = static_cast<int>(random(0, nb_jobs - 1));
      idx4 = static_cast<int>(random(0, nb_machines - 1));
    }
    // the maximum removable work is the minimum of the two durations
    // minus 1 to avoid creating tasks of length 0
    const int removable = MIN(ptime_arr[calTaskIdx(idx1, idx2)], ptime_arr[calTaskIdx(idx3, idx4)]) - 1;
    // fixed part that must be removed
    const int must_remove = static_cast<int>(trunc(removable * fixed_percentage));
    // random duration to be remove
    const int removed = must_remove + static_cast<int>(random(0, removable - must_remove));
    // substract from the removed frmo the first two tasks
    ptime_arr[calTaskIdx(idx1, idx2)] -= removed;
    ptime_arr[calTaskIdx(idx3, idx4)] -= removed;

    // add the amount removed from the two first tasks,
    // to keep all line sums equal to K
    ptime_arr[calTaskIdx(idx1, idx4)] += removed;
    ptime_arr[calTaskIdx(idx3, idx2)] += removed;
  }

  // calculate the workload of the instance
  // classical lower bound
  std::vector<int> machine(nb_machines);
  float pttot = 0;
  for (int i = 0; i < nb_jobs; i++)
  {
    int job = 0;
    for (int j = 0; j < nb_machines; j++)
    {
      // add processing time on scheduled machine
      machine[j] += ptime_arr[calTaskIdx(i, j)];
      job += ptime_arr[calTaskIdx(i, j)];
      pttot += ptime_arr[calTaskIdx(i, j)];
    }
    lower_bound = MAX(lower_bound, job);
  }
  for (int j = 0; j < nb_machines; j++)
  {
    lower_bound = MAX(lower_bound, machine[j]);
  }
  workload = float((pttot / (nb_machines * lower_bound)));

  // create an instance name
  int num = i + 1;
  ss_name << "hossp_"
          << std::setfill('0') << std::setw(2) << nb_jobs
          << "_"
          << std::setfill('0') << std::setw(2) << nb_machines;

  ss_instance << "// k = " << k
              << ", pert = " << nb_perturbations
              << ", fix = " << fixed_percentage
              << ", seed = " << random.seed()
              << ", lb = " << lower_bound
              << ", workload = " << workload
              << "\n";

  ss_instance << nb_jobs << " " << nb_machines << "\n";
  for (int i = 0; i < nb_jobs; ++i)
  {
    for (int j = 0; j < nb_machines; ++j)
    {
      ss_instance << ptime_arr[calTaskIdx(i, j)] << " ";
    }
    ss_instance << "\n";
  }
  ss_instance << "\n";

  return std::make_pair(ss_name.str(), ss_instance.str());
}

int main(int argc, char **argv)
{
  try
  {
    cxxopts::Options option("hossp", "hossp: Generate hard random instances of the open shop problem");
    option.add_options()("h,help", "This help message and exit")
                        ("n,jobs", "number of jobs", cxxopts::value<int>()->default_value("0"))
                        ("m,machines", "number of machines", cxxopts::value<int>()->default_value("0"))
                        ("k", "the k value, =rand(n*m,n*m*100) if 0", cxxopts::value<int>()->default_value("0"))
                        ("f,fix", "fixed percentage, =rand(0,1) if 0", cxxopts::value<double>()->default_value("0"))
                        ("p,pert", "number of perturbations, =rand(n*m,n*m^2) if 0", cxxopts::value<int>()->default_value("0"))
                        ("g,generate", "number of instances to generate", cxxopts::value<int>()->default_value("1"))
                        ("o,out", "Enable stdout", cxxopts::value<bool>()->default_value("false"))
                        ("d,dir", "Output directory", cxxopts::value<std::string>()->default_value(""))
                        ("s,seed", "random seed.", cxxopts::value<long>()->default_value("0"))
                        ;

    if (argc == 1)
    {
      std::cout << option.help();
      exit(1);
    }
    auto option_parse = option.parse(argc, argv);
    if (option_parse.count("help"))
    {
      std::cout << option.help();
      exit(1);
    }
    if (option_parse["jobs"].as<int>() <= 0)
    {
      std::cerr << "Error: option jobs must be > 0" << std::endl;
      exit(1);
    }
    if (option_parse["machines"].as<int>() <= 0)
    {
      std::cerr << "Error: option machines must be > 0" << std::endl;
      exit(1);
    }
    if (option_parse["pert"].as<int>() < 0)
    {
      std::cerr << "Error: option pert must be >= 0" << std::endl;
      exit(1);
    }
    if (option_parse["fix"].as<double>() < 0 or option_parse["fix"].as<double>() > 1)
    {
      std::cerr << "Error: option fix must be >= 0.0 and <= 1.0" << std::endl;
      exit(1);
    }
    if (option_parse["k"].as<int>() < 0)
    {
      std::cerr << "Error: option k must be >= 0" << std::endl;
      exit(1);
    }
    if (option_parse["g"].as<int>() <= 0)
    {
      std::cerr << "Error: option generate must be > 0" << std::endl;
      exit(1);
    }

    if (option_parse["seed"].as<long>() < 0)
    {
      std::cerr << "Error: option seed must be >= 0" << std::endl;
      exit(1);
    }

    if (option_parse["g"].as<int>() > 1 and option_parse["seed"].as<long>() > 0)
    {
      std::cerr << "Error: cannot define a random seed to generate multiple instances." << std::endl;
      exit(1);
    }

    bool is_stdout = option_parse["out"].as<bool>();
    std::string out_dir = option_parse["dir"].as<std::string>();

    int nb_jobs = option_parse["jobs"].as<int>();
    int nb_machines = option_parse["machines"].as<int>();

    int k = option_parse["k"].as<int>();
    int nb_perturbations = option_parse["pert"].as<int>();
    double fixed_percentage = option_parse["fix"].as<double>();

    int nb_instances = option_parse["g"].as<int>();
    long seed = option_parse["seed"].as<long>();

    // print instance statistics
    std::string logdir = "./log/";
    fs::create_directories(logdir);
    std::ofstream out_stats;
    std::string stats_filename = logdir + "stats.txt";
    out_stats.open(stats_filename, std::ofstream::out | std::ofstream::app);
    for (int i = 0; i < nb_instances; ++i)
    {
      float workload = 0.0;
      int lower_bound = 0;

      const std::pair<std::string, std::string> instance = generateInstance(i, nb_jobs, nb_machines, k, nb_perturbations, fixed_percentage, seed, lower_bound, workload);

      if (is_stdout)
        std::cout << instance.second;
      if (not(is_stdout and out_dir.empty()))
      {
        // renumber instances according to existing instances
        std::stringstream instance_filename_ss;
        int num = i + 1;
        instance_filename_ss << out_dir << instance.first << "_" << std::setfill('0') << std::setw(2) << num << ".txt";

        while (fs::exists(instance_filename_ss.str()))
        {
          num++;
          instance_filename_ss.clear();
          instance_filename_ss.str(std::string());
          instance_filename_ss << out_dir << instance.first << "_" << std::setfill('0') << std::setw(2) << num << ".txt";
        }

        // print stats on created instance
        if (out_stats.is_open())
        {
          out_stats << instance.first << "_" << std::setfill('0') << std::setw(2) << num
                    << ","
                    << k
                    << ","
                    << nb_perturbations
                    << ","
                    << fixed_percentage
                    << ","
                    << seed
                    << ","
                    << lower_bound
                    << ","
                    << workload
                    << "\n";
        }
        else
        {
          std::cerr << "Error: cannot open file " << stats_filename << std::endl;
        }

        // print the instance to a text file
        std::ofstream instance_file;
        instance_file.open(instance_filename_ss.str(), std::ofstream::out);
        if (instance_file.is_open())
        {
          instance_file << instance.second;
          instance_file.close();
        }
      }
      // reset seed
      seed = 0;
      nb_perturbations = 0;
      fixed_percentage = 0;
    }

    out_stats.close();
  }
  catch (const std::exception &e)
  {
    std::cerr << e.what() << std::endl;
    exit(1);
  }
  return 0;
}