#include "formats/cube.hpp"
#include "formats/gnuplot.hpp"
#include "formats/stm.hpp"
#include "atomistic/fundamental.hpp"
#include "types.hpp"
#include "wrappers/lapack.hpp"
#include "io.hpp"
#include <cmath>

#include <ctime>

#include <boost/format.hpp>
#include <boost/spirit/include/qi_core.hpp>
#include <boost/spirit/include/qi_eol.hpp>
#include <boost/lexical_cast.hpp>

#include <blitz/array.h>

#include <fftw3.h>

namespace formats {
namespace stm {

using namespace types;
using namespace wrappers::lapack;

bool StsCube::initialize(){
    if(levels.size() == 0){
        std::cout
            << "No states for given energy window. STS will not be performed.";
        return false;
    }

    // Steal from first cube file
    std::list< formats::WfnCube >::const_iterator
        cubeIt = levels.begin();

    // Get atoms and proper grid
    this->fileName = cubeIt->fileName;
    this->readCubeFile();

    this->title  = "STS data (z = energy)";
    this->description  = str(boost::format(
                "Range [%1.3d V,%1.3d V], delta %1.4d V, sigma %1.4d V")
            % eMin % eMax % deltaE % sigma);

    if(modeFlag == CONSTANT_Z){
        Real height = boost::lexical_cast<Real>(modeParameter);

        // Get z index of STS plane
        std::vector<types::Real> tempvector;
        tempvector.push_back(0);
        tempvector.push_back(0);
        tempvector.push_back(this->topZCoordinate() + height);
        std::vector<types::Uint> indices;
        this->grid.getNearestIndices(tempvector, indices);
        this-> zIndex = indices[2];
        std::cout << "STS will be performed at z-index "
            << zIndex << "\n";
        this->title += str(boost::format(" at z-plane %3d") % zIndex);
    }
    else if (modeFlag == PROFILE){
        stm = stm::StmCube();
        std::cout << "Reading z profile from "
            << modeParameter << "\n";
        stm.readIgorFile(modeParameter);
        this->title += " on z profile read from ";
        this->title += modeParameter;
    }

    // Adjust z dimension for energy
    Uint newIncrementCount =(unsigned int) ((eMax - eMin)/deltaE) + 1;

    if(this->cubeZ == 0){
        // Per default we simply keep the old z extent
        this->grid.directions[2].scaleVector(
                Real(grid.directions[2].getNElements())/
                Real(newIncrementCount));
        this->grid.directions[2] =
            la::Direction(grid.directions[2].getIncrementVector(),
                    newIncrementCount);
    }
    else{
        // Else the new extent is cubeZ atomic units
        Real newDZ = this->cubeZ / newIncrementCount;
        std::vector<Real> v; v.push_back(0); v.push_back(0); v.push_back(newDZ);
        this->grid.directions[2] = la::Direction(v, newIncrementCount);
        this->grid.originVector[2] =
            eMin / (eMax - eMin) * cubeZ;
    }
    grid.data = std::vector<Real>(grid.countPoints(), 0.0);

    return true;
}

bool StsCube::calculate(){

    if( ! this->initialize() ) return false;

    std::list< formats::WfnCube >::const_iterator
        cubeIt = levels.begin(),
    cubeEnd = levels.end();

    // Get all necessary planes
    formats::WfnCube tempCube;
    while(cubeIt != cubeEnd){
        std::cout<< "Processing " << cubeIt->fileName << "\n";
        tempCube = *cubeIt;
        tempCube.readCubeFile();
        if(!this->psiSquared) tempCube.squareValues();
        std::vector<Real> tempPlane;

        if(modeFlag == CONSTANT_Z){
           tempCube.getZPlane(zIndex, tempPlane);
        }
        else if(modeFlag == PROFILE){
            this->interpolateOnZProfile(tempCube.grid, tempPlane);
        }

        addLevel(tempPlane, cubeIt->energy);
        ++cubeIt;
    }

    std::cout<< "Done processing cube files...\n\n";

    // Normalize sum, s.th. values are, on average, 1
    //grid *= Real(grid.countPoints()) / grid.sum();

    return true;
}

void StsCube::interpolateOnZProfile(
        const formats::CubeGrid &grid,
        std::vector<Real> &result) const {
    Uint nX = grid.directions[0].getNElements();
    Uint nY = grid.directions[1].getNElements();
    result.reserve(nX * nY);

    Real dZ = grid.directions[2].getIncrementVector()[2];
    std::vector<Real>::const_iterator zIt = this->stm.stm.begin();
    Real valLow, valHigh, deltaLow;
    Uint zLow;
    for(Uint x = 0; x < nX; ++x){
        for(Uint y = 0; y < nY; ++y){
            zLow = Uint(*zIt / dZ);
            deltaLow = (*zIt - zLow * dZ) / dZ; // Between 0 and 1
            valLow = grid.getDataPoint(x, y, zLow);
            valHigh = grid.getDataPoint(x, y, zLow + 1);

            result.push_back(valLow * (1.0 - deltaLow) + valHigh * deltaLow);

            ++zIt;
        }
    }
}

void StsCube::addLevel(const std::vector<types::Real> &plane,
               types::Real energy){

    Uint nEnergies = grid.directions[2].getNElements();

    // Gaussian stuff
    // \$ \frac{1}{\sigma \sqrt{2\pi} e^{-\frac{(x-\mu)^2}{2\sigma^2}} \$
    // = a e^{c(x-b)^2}
    types::Real a = 1.0/(sigma * std::sqrt(2 * M_PI));
    types::Real c = -1.0/(2 * sigma * sigma);

     std::vector<Real>::const_iterator
         planeIt = plane.begin(),
         planeEnd = plane.end();
     std::vector<Real>::iterator
         dataIt = grid.data.begin();
     while(planeIt != planeEnd){
         for(Uint z = 0; z < nEnergies; ++z){
             Real e = eMin + deltaE * z;
             *dataIt += *planeIt * a * std::exp(c * (energy - e)*(energy - e));
             ++dataIt;
         }
         ++planeIt;
     }

}

void StmCube::setIsoValue(types::Real isoValue){
    this->setValue = isoValue;
    this->mode = CONSTANT_CURRENT;
    this->stm.clear();
    getZIsoSurface(isoValue, this->stm);
}

void StmCube::setZValue(types::Real zValue){
    this->setValue = zValue;
    this->mode = CONSTANT_HEIGHT;
    this->stm.clear();

    // Find largest z coordinate of atoms
    std::vector<types::Real> tempvector;
    tempvector.push_back(0);
    tempvector.push_back(0);
    tempvector.push_back(this->topZCoordinate() + zValue);
    std::vector<types::Uint> indices;
    this->grid.getNearestIndices(tempvector, indices);
    Uint zIndex = indices[2];
    std::cout << "STM will be performed at z-index "
        << zIndex << "\n";
    this->getZPlane(zIndex, this->stm);
    std::cout << "STM will be performed at z-index "
        << zIndex << "\n";
}

bool StmCube::writeIgorFile(String fileName) const {
    Uint nX = grid.directions[0].getNElements();
    Uint nY = grid.directions[1].getNElements();
    std::vector<Real>::const_iterator stmIt = stm.begin();

    Stream result = "";
    for(Uint x = 0; x<nX; ++x){
        for(Uint y=0; y<nY; ++y){
            result += str(boost::format("%12.6e") % *stmIt);
            result += " ";
            ++stmIt;
        }
        result += "\n";
    }

    return io::writeStream(fileName, result);
}

bool StmCube::readIgorFile(String fileName) {
    String content;
    io::readFile(fileName, content);

    std::string::const_iterator it = content.begin(),
        end = content.end();

    using boost::spirit::double_;
    using boost::spirit::qi::eol;
    using boost::spirit::qi::parse;
    using boost::spirit::ascii::space;
    if (! parse(
        it,
        end,
        double_ % space,
        stm
        )) throw types::parseError() << types::errinfo_parse("title or description");

   return true;
}

WfnExtrapolation::WfnExtrapolation(
        std::vector<String> fileNames,
        const cp2k::Spectrum& spectrum,
        Cube&   hartree ,
        Mode          mode    ,
        types::Real   var1    ,
        types::Real   zWidth  ,
        types::Real   approachFrom,
        types::Real   kPlaneMax,
        types::Uint   nLayers
        ){

    for(std::vector<String>::const_iterator it = fileNames.begin();
                        it != fileNames.end(); ++it){
        WfnCube tmp;
        tmp.setFileName(*it);
        wfns.push_back(tmp);
    }

    this->spectrum = spectrum;
    this->hartree = hartree;
    this->mode    = mode;
    if      ( mode == plane )       this->zStart     = var1;
    else if ( mode == rollingBall ) this->ballRadius = var1;
    else if ( mode == isoSurface )  this->isoValue   = var1;
    this->zWidth  = zWidth;
    this->approachFrom = approachFrom;
    this->kPlaneMax = kPlaneMax;
    this->nLayers     = nLayers;

}

void WfnExtrapolation::execute(){
    using namespace blitz;
    time_t t;

    this->determineRange();

    // Need some info on the grid
    // SI units energy: 2 m E/hbar^2 a0 = 2 E/Ha
    Uint nX = hartree.nX(), nY = hartree.nY(), nZ = zEndIndex + 1;
    Real dX = hartree.dX(), dY = hartree.dY(), dZ = hartree.dZ();

    std::cout << "Original wave vector grid: kx,ky = "
        << nX << "," << nY << "\n";
    int nKX = nX, nKY = nY, nK = nKX * nKY;
    if (mode == isoSurface || mode == rollingBall){
        // May choose to reduce Fourier components
        // limiting oscillation frequency/a.u. to kMax
        Real dKX = 2 * M_PI / (dX * Real(nX));
        Real dKY = 2 * M_PI / (dY * Real(nY));

        Real kXMax = nKX/2.0 * dKX;
        Real kYMax = nKY/2.0 * dKY;
        Real scale = this->kPlaneMax / sqrt(kXMax * kXMax + kYMax * kYMax);
        if( scale < 1.0 ){
            nKX = int( scale * nX);
            nKY = int( scale * nY);
            std::cout << "Reduced wave vector grid: kx,ky = " << nKX << "," << nKY << "\n";
        }

    }

    for(std::vector<WfnCube>::iterator wfn = wfns.begin();
            wfn != wfns.end(); ++wfn){

        t = clock();
        std::cout << "--------\n Processing  " << wfn->getFileName() << "\n";
        wfn->readCubeFile();
        std::cout << "Time to read cube : " << (clock() -t)/1000.0 << " ms\n";

        wfn->grid.resize(nX, nY, nZ);
        Array<types::Real,3> dataArray(
                &(wfn->grid.data[0]),
                shape(nX, nY, nZ),
                neverDeleteData);
        Real E =  spectrum.getLevel(wfn->getSpin(), wfn->getLevel());
        E -= this->surfacePotential;
        wfn->setEnergy(E);
        std::cout << "Energy level " << wfn->getEnergy() << " Ha\n";

        // Produce z profile
        std::string zProfile = io::getFileName(wfn->getFileName());
        WfnCube tmp = WfnCube(*wfn);
        tmp.squareValues();
        zProfile += ".zprofile";
        std::cout << "Writing Z profile to " << zProfile << std::endl;
        tmp.writeZProfile(zProfile, "Z profile of sqared wave function\n");

        // Get values on extrapolation surface
        wfn->grid.zSurface(this->zIndices, this->surface);
        types::String s = formats::gnuplot::writeMatrix<Real>(this->surface, nX, nY);
        String sFile = io::getFileName(wfn->getFileName()) + ".exsurf";
        io::writeStream(sFile, s);

        std::cout << "Starting extrapolation ... " << std::endl;
        if ( mode == plane )
            this->onPlane(*wfn);

        else if ( mode == isoSurface || mode == rollingBall )
            this->onSurface(*wfn, nKX, nKY);

        std::cout << "Time to extrapolate : " << (clock() -t)/1000.0 << " ms\n";

        types::String outCubeFile = "extrapolated.";
        outCubeFile += io::getFileName(wfn->getFileName());
        t = clock();
        std::cout << "Writing extrapolated cube file to " << outCubeFile << std::endl;
        wfn->writeCubeFile(outCubeFile);
        std::cout << "Time to write cube : " << (clock() -t)/1000.0 << " ms\n";

        // Produce z profile
        tmp = *wfn;
        tmp.grid.squareValues();
        std::string outZProfile = outCubeFile;
        outZProfile += ".zprofile";
        std::cout << "Writing Z profile of extrapolated cube file to " << outZProfile << std::endl;
        tmp.writeZProfile(outZProfile, "Z profile of squared extrapolated wave function\n");

        wfn->grid.data.clear();
    }
}

void WfnExtrapolation::determineRange(){
    using namespace blitz;

    Cube tmp(hartree);

    // Some preparations...
    if( mode == plane || mode == rollingBall) {
        // Find highest z-coordinate
        // Note: The slowest index in cube file format is x, so in terms of storage
        //       modifying the number of x-coordinates would be the easiest.
        std::vector< atomistic::Atom >::const_iterator it = hartree.atoms.begin(),
            end = hartree.atoms.end();
        types::Real zTop = it->coordinates[2];
        ++it;
        while(it != end) {
            if( it->coordinates[2] > zTop ) {
                zTop = it->coordinates[2];
            }
            ++it;
        }

        // Define region of interpolation
        std::vector<types::Real> tempvector;
        tempvector.push_back(0);
        tempvector.push_back(0);
        tempvector.push_back(zTop + zStart);
        std::vector<types::Uint> indices;
        hartree.grid.getNearestIndices(tempvector, indices);
        this->zStartIndex = indices[2];
        this->zSurfEndIndex = indices[2];

        tempvector[2] += zWidth;
        hartree.grid.getNearestIndices(tempvector, indices);
        this->zEndIndex = indices[2];
    }

    // Define the z indices
    if (mode == plane){
        std::cout << "Fourier transform will be performed at z-index "
            << zStartIndex << "\n";

        this->zIndices = std::vector<Uint> (hartree.nX() * hartree.nY(), zStartIndex);

    }

    else if ( mode == isoSurface ) {
        if (approachFrom < 0) hartree.grid.zIsoSurfaceOnGrid(isoValue, this->zIndices);
        else                  hartree.grid.zIsoSurfaceOnGrid(isoValue, this->zIndices, approachFrom);

        Array<types::Uint,2> zIndicesBlitz(
                &zIndices[0],
                shape(hartree.nX(), hartree.nY()),
                neverDeleteData);

        // Write some info about plane
        zStartIndex = min(zIndicesBlitz);
        Real dZ = hartree.grid.directions[2].getIncrementVector()[2];
        zSurfEndIndex = max(zIndicesBlitz);
        zEndIndex = zSurfEndIndex + Uint(zWidth / dZ);

        std::cout << "Isosurface ranges over " << (zSurfEndIndex-zStartIndex)*dZ
                  << " from z = " << zStartIndex * dZ
                  << " to " << max(zIndicesBlitz) * dZ << " [a.u.].\n";
        std::cout << "Extrapolation will be extended to "
                  << zEndIndex * dZ << " [a.u.]\n";
    }

    else if (mode == rollingBall){
        Uint nX = hartree.nX(), nY = hartree.nY(), nZ = zEndIndex + 1;
        Real dX = hartree.dX(), dY = hartree.dY(), dZ = hartree.dZ();

        // TODO if (approachFrom > 0) zMax  = approachFrom;

        std::vector<bool> boolGrid(nX*nY*nZ, false);
        std::vector<atomistic::Atom>::const_iterator
            it  = hartree.atoms.begin(),
            end = hartree.atoms.end();
        int rI = int(ballRadius / dX) + 1;
        int rJ = int(ballRadius / dY) + 1;

        Real x,y,z;
        int iX, iY, iZ;
        Real r2 = ballRadius * ballRadius;

        // Build the boolean Grid
        std::vector<types::Uint> indices;
        std::vector<Real> coords;
        while(it != end){
            indices.clear();
            coords = it->getCoordinates();
            hartree.grid.getNearestIndices(coords, indices);

            for(int i = -rI; i <= rI; ++i)
                for(int j = -rJ; j <= rJ; ++j){
                    x = i * dX;
                    y = j * dY;
                    z = r2 - x*x - y*y;

                    if (z > 0){
                        iX = (indices[0] + i) % nX;
                        iY = (indices[1] + j) % nY;
                        iZ = int(indices[2]) + int(sqrt(z) / dZ);
                        if (iZ > zEndIndex) iZ = zEndIndex;

                        boolGrid[iX*nY*nZ + iY*nZ + iZ] = true;
                    }
                }

            ++it;
        }

        // Find the topmost 'true' surface
        zIndices = std::vector<Uint>(nX*nY);
        for(int i = 0; i < nX; ++i)
            for(int j = 0; j < nY; ++j){
                int k = nZ;
                while(k > 0){
                    --k;
                    if (boolGrid[i*nY*nZ + j*nZ + k] == true){
                        zIndices[i*nY + j] = k;
                        break;
                    }
                }
                if (k == 0){
                    std::cout << "Warning: Ball rolled to bottom of cube file without finding an atom.\n";
                    zIndices[i*nY + j] = 0;
                }
            }

//        Old, much too slow way of doing things
//        std::vector<Real> tmp;
//        for(Uint x = 0; x < nX; ++x){
//            std::cout << "Here\n";
//            for(Uint y = 0; y < nY; ++y)
//                for(Uint z = zMax; z > 0; --z){
//                    tmp.push_back(x*hartree.dX());
//                    tmp.push_back(y*hartree.dY());
//                    tmp.push_back(z*hartree.dZ());
//                    if(hartree.distance(tmp) < ballRadius){
//                        zIndices[y*nX+x] = z-1;
//                        break;
//                    }
//                }
//        }

        Array<types::Uint,2> zIndicesBlitz(
                &zIndices[0],
                shape(hartree.nX(), hartree.nY()),
                neverDeleteData);

        // Write some info about plane
        zStartIndex = min(zIndicesBlitz);
        zSurfEndIndex = max(zIndicesBlitz);
        //Real dZ = hartree.dZ();
        zEndIndex = zSurfEndIndex + Uint(zWidth / dZ);

        std::cout << "Rolling-ball surface ranges over " << (zSurfEndIndex-zStartIndex)*dZ
                  << " from z = " << zStartIndex * dZ
                  << " to " << zSurfEndIndex * dZ << " [a.u.].\n";
        std::cout << "Extrapolation will be extended to "
                  << zEndIndex * dZ << " [a.u.]\n";

    }

    // Write extrapolation z indices
    String s = formats::gnuplot::writeMatrix<Uint>(this->zIndices, hartree.nX(), hartree.nY());
    String sFile = io::getFileName(hartree.getFileName()) + ".zindices";
    io::writeStream(sFile, s);

    this->setSurfacePotential();

}

void WfnExtrapolation::setSurfacePotential(){
    using namespace blitz;

    std::vector<Real> hartreeVector;
    hartree.grid.zSurface(zIndices, hartreeVector);
    Array<Real, 2> hartreeSurface (
            &hartreeVector[0],
            shape(hartree.nX(), hartree.nY()),
            neverDeleteData);
    this->surfacePotential = mean(hartreeSurface);
    Array<Real, 3> hartreeGrid (
            &(hartree.grid.data[0]),
            shape(hartree.nX(), hartree.nY(), hartree.nZ()),
            neverDeleteData);
    Real vacuumPotential = max(hartreeGrid);

    std::cout << "Average Hartree potential on extrapolation surface is "
              << surfacePotential << " Ha\n";
    std::cout << "This is " << vacuumPotential - surfacePotential
              << " Ha below the maximum Hartree potential.\n";

    // Print hartree potential for gnuplot
    types::String s = formats::gnuplot::writeMatrix(hartreeVector, hartree.nX(), hartree.nY());
    String surfaceFile = io::getFileName(hartree.getFileName()) + ".exsurf";
    io::writeStream(surfaceFile, s);

}

void WfnExtrapolation::onPlane(WfnCube& wfn){
    using namespace blitz;
    time_t t;

    // Need some info on the grid
    // SI units energy: 2 m E/hbar^2 a0 = 2 E/Ha
    Uint nX = wfn.nX(), nY = wfn.nY(), nZ = wfn.nZ();
    Real dX = hartree.dX(), dY = hartree.dY(), dZ = hartree.dZ();
    Real dKX = 2 * M_PI * dZ / (dX * Real(nX));
    Real dKY = 2 * M_PI * dZ / (dY * Real(nY));

    Real E = wfn.getEnergy() * dZ * dZ;
    Array<types::Real,3> dataArray(
            &(wfn.grid.data[0]),
            shape(nX, nY, nZ),
            neverDeleteData);

    // Do a real 2 complex fft
    // Since a(-k)=a(k)^* (or in terms of indices: a[n-k]=a[k]^*)
    // only a[k], k=0...n/2+1 (division rounded down) are retained
    types::Uint nXF = nX, nYF = nY/2 + 1;
    fftw_complex *out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * nXF * nYF);

    Array<types::Real,2> planeDirect(
            &surface[0],
            shape(nX, nY),
            neverDeleteData);

    fftw_plan plan_forward = fftw_plan_dft_r2c_2d(nX, nY, planeDirect.data(), out, FFTW_ESTIMATE);
    fftw_execute(plan_forward);

    Array<types::Complex,2> planeFourier(
            reinterpret_cast<types::Complex *>(out),
            shape(nXF, nYF),
            neverDeleteData);

    std::vector<Real> fourierReal(nXF*nYF);
    for(int i =0;i<nXF;++i)
        for(int j =0;j<nYF;++j)
            fourierReal[i*nYF+j] = abs(planeFourier(i,j));

    // Plotting the coefficients of the Fourier
    String s = formats::gnuplot::writeMatrix<Real>(fourierReal, nXF, nYF);
    String hartleyFile = io::getFileName(wfn.getFileName()) + ".fourier";
    io::writeStream(hartleyFile, s);

    // Calculate the exponential prefactors.
    // Multiplication of Fourier coefficients with these prefactors
    // propagates them to next z plane.
    // The prefectors would only need dimensions (nX/2 +1, nY/2 +1)
    // since they are the same for G and -G. However for the sake of
    // simplicity of calculation, we prepare them here for (nX, nY/2+1) = (nXF,nYF).
    t = clock();
    Array<types::Real,2> prefactors(nXF, nYF);

    // Notice that the order of storage is 0...G -G...-1 for uneven nX
    // and 0...G-1 G -(G-1)...-1 for even NX
    prefactors( Range(0, nXF/2), Range::all())= exp(- sqrt(tensor::i * dKX * tensor::i * dKX + tensor::j *dKY * tensor::j * dKY - 2 * E) );
    // tensor::i always starts from 0, i.e. it ranges from 0...nXF/2-1 (nX even) or 0...nXF/2 (nX uneven)
    if(nX % 2 == 1)
        prefactors( Range(nXF/2 + 1, nXF - 1), Range::all())= exp(- sqrt( (nXF/2 - tensor::i) * dKX * (nXF/2 - tensor::i) * dKX + tensor::j *dKY * tensor::j * dKY - 2 * E) );
    else
        prefactors( Range(nXF/2 + 1, nXF - 1), Range::all())= exp(- sqrt( (nXF/2 -1 - tensor::i) * dKX * (nXF/2 - 1 - tensor::i) * dKX + tensor::j *dKY * tensor::j * dKY - 2 * E) );

    // Sequentially update the cube file
    Array<types::Real,2> tempDirect(nX, nY);
    Array<types::Complex,2> tempFourier(nXF, nYF);
    fftw_plan plan_backward;
    for(int zIndex = zStartIndex + 1; zIndex < nZ; ++zIndex) {
        // Propagate fourier coefficients
        planeFourier = prefactors(tensor::i, tensor::j) * planeFourier(tensor::i, tensor::j);

        // The c2r transform destroys its input
        tempFourier = planeFourier;
        plan_backward = fftw_plan_dft_c2r_2d(nX, nY, (fftw_complex*) tempFourier.data(), tempDirect.data(), FFTW_ESTIMATE);

        // Do Fourier-back transform
        fftw_execute(plan_backward); /* repeat as needed */
        tempDirect /= nX * nY;

        // Copy data
        dataArray(Range::all(), Range::all(), zIndex) = tempDirect(Range::all(), Range::all());
    }

    fftw_destroy_plan(plan_forward);
    fftw_destroy_plan(plan_backward);
    fftw_free(out);
}

void WfnExtrapolation::onSurface(WfnCube& wfn, Uint nKX, Uint nKY){
    using namespace blitz;
    time_t t;

    // Need some info on the grid
    // SI units energy: 2 m E/hbar^2 a0 = 2 E/Ha
    Uint nX = hartree.nX(), nY = hartree.nY(), nZ = zEndIndex + 1;
    Real dX = hartree.dX(), dY = hartree.dY(), dZ = hartree.dZ();
    Real dKX = 2 * M_PI * dZ / (dX * Real(nX));
    Real dKY = 2 * M_PI * dZ / (dY * Real(nY));

    Array<types::Real,3> dataArray(
            &(wfn.grid.data[0]),
            shape(nX, nY, nZ),
            neverDeleteData);
    Real E = wfn.getEnergy() * dZ * dZ;

    int nK = nKX * nKY;

    // Instead of a wave-number based criterion that is the
    // same for all wave functions, we may also choose some adaptive
    // criterion such as the one below (upper limit for z-decay of
    // involved basis functions.
//    // May choose to reduce k-grid
//    Real kXMax = nKX/2.0 * dKX;
//    Real kYMax = nKY/2.0 * dKY;
//    // Take only low-freq. Fourier components such that no basis function
//    // decays faster than f(z) \propto 10^{-minExpGoal * z}
//    Uint zSpan = zSurfEndIndex - zStartIndex;
//
//    // Translate decayCutoff to minimum exponent per z-index
//    Real minExpGoal = this->decayCutoff * dZ * log(10.0);
//    Real scaling = sqrt( (minExpGoal * minExpGoal + 2*E)
//            / (kXMax * kXMax + kYMax * kYMax) );
//    if(scaling < 1.0){
//        nKX = int( scaling * nX); kXMax = nKX/2.0 * dKX;
//        nKY = int( scaling * nY); kYMax = nKY/2.0 * dKY;
//        std::cout << "Reduced wave vector grid: kx,ky = " << nKX << "," << nKY << "\n";
//    }
//    Real minExp = - sqrt( -2.0 * E + kXMax * kXMax + kYMax * kYMax);
//    std::cout << "Strongest considered decay: x " << exp(minExp/dZ) << " per a.u.\n";
//    minExp *= Real(zSurfEndIndex) - Real(zStartIndex);
//    std::cout << "Smallest exponential term in matrix: " << exp(minExp) << "\n";

    // Build matrix for modified discrete Hartley transform
    Uint n = nX * nY;
    std::cout << "Matrix dimension is " << n << "x" << nK
        << ", i.e. " << Real(nLayers*n*nK*8)  / (1024.0 * 1024.0) << " MBytes per matrix\n";
    //exit(0);
    //        // Test: Extrapolation on plane
    //        for(int i = 0; i < zIndices.size();++i){
    //            zIndices[i] = 128;
    //        }
    //        this->zSurfEndIndex = 128;
    //        this->zStartIndex = 128;
    //        E = -0.160023 * dZ * dZ;

    std::vector<Real> A(n*nK*nLayers);
    int iX, iY, jX, jY;
    Real kX, kY, kZ;
    Real arg;
    t = clock();

    for(Uint j = 0; j < nK; ++j){
        jX = j / nKY; jX = (2* jX < nKX) ? jX : jX - nKX;
        jY = j % nKY; jY = (2* jY < nKY) ? jY : jY - nKY;

        kX = jX * dKX;
        kY = jY * dKY;
        kZ = sqrt( -2.0 * E + kX * kX+ kY * kY);

        for(Uint i = 0; i < n; ++i){
            iX = i / nY; iY = i % nY;
            arg = 2.0 * M_PI * ( Real(iX * jX) / Real(nX)  + Real(iY * jY) / Real(nY) );

            for(Uint l = 0; l < nLayers; ++l){
                // Need transposed matrix for Fortran
                A[l*n+i + j*n*nLayers] = (sin(arg) + cos(arg)) *
                    exp(- kZ * (int(zIndices[i]) - int(l) - int(zStartIndex) ) );
            }
        }
    }
    std::cout << "Matrix created in : " << (clock() -t)/1000.0 << " ms\n";

    // Prepare wave function values
    int iZ;
    std::vector<Real> hartley(n*nLayers);
    for(Uint l = 0; l < nLayers; ++l){
        for(Uint i = 0; i < n; ++i){
            iX = i/nY;
            iY = i%nY;
            iZ = int(zIndices[i]) - int(l);
            hartley[l*n + i] = wfn.grid.data[iX*nY*nZ+ iY*nZ + iZ];
        }
    }

    // Solve the linear system
    char TRANS_ = 'N';
    int  M_     = n*nLayers;
    int  N_     = nK;
    int  NRHS_  = 1;
    std::vector<Real> S(N_);
    Real RCOND_ = 1e-5;
    int  RANK_;
    std::vector<Real> WORK(1);
    int  LWORK_ = -1;
    std::vector<int> IWORK(1);
    int  ILWORK_ = -1;
    int  INFO_  = 0;
    // Writes correct WORK dimensions to WORK[0], IWORK[0]
    // SUBROUTINE DGELSD( M, N, NRHS, A, LDA, B, LDB, S, RCOND, RANK,
    //                   WORK, LWORK, IWORK, INFO )
    dgelsd_(&M_, &N_, &NRHS_,
            &*A.begin(), &M_,
            &*hartley.begin(), &M_,
            &*S.begin(), &RCOND_, &RANK_,
            &*WORK.begin(), &LWORK_, &*IWORK.begin(),
            &INFO_);
    LWORK_ = int(abs(WORK[0]));
    WORK =  std::vector<Real>(LWORK_);
    ILWORK_ = int(abs(IWORK[0]));
    IWORK =  std::vector<int>(ILWORK_);

    // Now solve this little puzzle
    std::cout << "Starting optimization of overdetermined system\n";
    t = clock();

    dgelsd_(&M_, &N_, &NRHS_,
            &*A.begin(), &M_,
            &*hartley.begin(), &M_,
            &*S.begin(), &RCOND_, &RANK_,
            &*WORK.begin(), &LWORK_, &*IWORK.begin(),
            &INFO_);
    //        dgels_(&TRANS_, &M_, &N_, &NRHS_,
    //                &*A.begin(), &M_,
    //                &*hartley.begin(), &M_,
    //                &*WORK.begin(), &LWORK_,
    //                &INFO_);
    if( INFO_ != 0 ){
        throw types::runtimeError()
            << types::errinfo_runtime("LAPACK Error: Problem with solution of least-squares problem.");
    }
    std::cout << "Found solution in : " << (clock() -t)/1000.0 << " ms\n";

    // Print condition number of problem
    std::cout << "Condition number: " << S[0] / S[nK -1] << "\n";

    //        // Plotting the coefficients of the hartley transform
    //        types::String s = formats::gnuplot::writeMatrix<Real>(hartley, nKX, nKY);
    //        String hartleyFile = io::getFileName(wfn->getFileName()) + ".hartley";
    //        io::writeStream(hartleyFile, s);

    // Re-layout hartley components to nX-nY
    std::vector<Real> tmp = hartley;
    hartley = std::vector<Real>(nX*nY, 0.0);
    Real i2, j2;
    for(int i = 0; i < nKX; ++i){
        i2 = (2 * i < nKX) ? i :  int(nX) + i - nKX ;
        for(int j = 0; j < nKY; ++j){
            j2 = (2 * j < nKY) ? j : int(nY) + j - nKY;
            hartley[i2 * nY + j2] = tmp[i* nKY + j];
        }
    }

    //        // Plotting the coefficients of the reformed hartley transform
    //        s = formats::gnuplot::writeMatrix<Real>(hartley, nX, nY);
    //        hartleyFile = io::getFileName(wfn->getFileName()) + ".hartley2";
    //        io::writeStream(hartleyFile, s);

    // Producing Fourier transform
    int nXF = nX; int nYF = nY/2 +1;
    std::vector<Complex> fourier(nXF*nYF);
    int iT,jT;
    for(int i = 0; i < nXF; ++i){
        for(int j = 0; j < nYF; ++j){
            iT = (int(nX) - i) % nX;
            jT = (int(nY) - j) % nY;

            fourier[i*nYF+j] =
                1/2.0 * (               hartley[i*nY+j] + hartley[iT*nY+jT]
                        - Complex(0,1.0)*( hartley[i*nY+j] - hartley[iT*nY+jT]) );
        }
    }

    // Plotting the coefficients of the Fourier transform
    std::vector<Real> fourierReal(fourier.size());
    for(int i =0;i<fourierReal.size();++i)
        fourierReal[i] = abs(fourier[i]);
    String s = formats::gnuplot::writeMatrix<Real>(fourierReal, nXF, nYF);
    String fileName = io::getFileName(wfn.getFileName()) + ".fourier";
    io::writeStream(fileName, s);

    // Calculating the exponential prefactors to propagate coefficients to
    // next z plane
    t = clock();
    std::vector<Real> pref(nXF*nYF);
    for(int i = 0; i < nXF; ++i){
        kX = (2*i < nX) ? i : i - int(nX) ;
        kX *= dKX;
        for(int j = 0; j < nYF; ++j){
            kY = (2*j < nY) ? j : j - int(nY);
            kY *= dKY;
            kZ = sqrt( -2.0 * E + kX * kX+ kY * kY);
            pref[i*nYF + j] = exp(- kZ );
        }
    }

    Array<types::Real,2> prefactors(
            &pref[0],
            shape(nXF, nYF),
            neverDeleteData);

    Array<types::Complex,2> planeFourier(
            &fourier[0],
            shape(nXF, nYF),
            neverDeleteData);

    Array<types::Complex,2> tempFourier(nXF, nYF);
    Array<types::Real,2> tempDirect(nX, nY);
    fftw_plan plan_backward;

    for(int zIndex = zStartIndex+1; zIndex < nZ; ++zIndex) {
        // Propagate hartley coefficients (could use matmul here)
        planeFourier = prefactors(tensor::i, tensor::j) * planeFourier(tensor::i, tensor::j);

        // The c2r transform destroys its input
        tempFourier = planeFourier;
        plan_backward = fftw_plan_dft_c2r_2d(
                nX, nY,
                (fftw_complex*) tempFourier.data(),
                tempDirect.data(),
                FFTW_ESTIMATE);

        // Perform Fourier transform backwards
        fftw_execute(plan_backward);

        // Copy data
        if (zIndex > zSurfEndIndex)
            dataArray(Range::all(), Range::all(), zIndex) = tempDirect(Range::all(), Range::all());
        else {
            for(int x = 0; x < nX; ++x){
                for(int y = 0; y < nY; ++y){
                    if (zIndex > zIndices[x*nY + y]) {
                        dataArray(x, y, zIndex) = tempDirect(x, y);
                    }
                }
            }
        }

    }
    fftw_destroy_plan(plan_backward);

}

}
}


///////////////////////////////////////////////////////////////////////////////
//  Copyright 2014 Anton Bikineev
//  Copyright 2014 Christopher Kormanyos
//  Copyright 2014 John Maddock
//  Copyright 2014 Paul Bristow
//  Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_HYPERGEOMETRIC_1F0_HPP
#define BOOST_MATH_HYPERGEOMETRIC_1F0_HPP

#include <boost/math/policies/policy.hpp>
#include <boost/math/policies/error_handling.hpp>

namespace boost { namespace math { namespace detail {

template <class T, class Policy>
inline T hypergeometric_1F0_imp(const T& a, const T& z, const Policy& pol)
{
   static const char* function = "boost::math::hypergeometric_1F0<%1%,%1%>(%1%, %1%)";
   BOOST_MATH_STD_USING // pow

   if (z == 1)
      return policies::raise_pole_error<T>(
         function,
         "Evaluation of 1F0 with z = %1%.",
         z,
         pol);
   if (1 - z < 0)
   {
      if (floor(a) != a)
         return policies::raise_domain_error<T>(function,
            "Result is complex when a is non-integral and z > 1, but got z = %1%", z, pol);
   }
   // more naive and convergent method than series
   return pow(T(1 - z), T(-a));
}

} // namespace detail

template <class T1, class T2, class Policy>
inline typename tools::promote_args<T1, T2>::type hypergeometric_1F0(T1 a, T2 z, const Policy&)
{
   BOOST_FPU_EXCEPTION_GUARD
   typedef typename tools::promote_args<T1, T2>::type result_type;
   typedef typename policies::evaluation<result_type, Policy>::type value_type;
   typedef typename policies::normalise<
      Policy,
      policies::promote_float<false>,
      policies::promote_double<false>,
      policies::discrete_quantile<>,
      policies::assert_undefined<> >::type forwarding_policy;
   return policies::checked_narrowing_cast<result_type, Policy>(
      detail::hypergeometric_1F0_imp<value_type>(
         static_cast<value_type>(a),
         static_cast<value_type>(z),
         forwarding_policy()),
      "boost::math::hypergeometric_1F0<%1%>(%1%,%1%)");
}

template <class T1, class T2>
inline typename tools::promote_args<T1, T2>::type hypergeometric_1F0(T1 a, T2 z)
{
   return hypergeometric_1F0(a, z, policies::policy<>());
}

  } } // namespace boost::math

#endif // BOOST_MATH_HYPERGEOMETRIC_1F0_HPP


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2018.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Timo Sachsenberg $
// $Authors: David Wojnar $
// --------------------------------------------------------------------------
//
#include <OpenMS/MATH/STATISTICS/PosteriorErrorProbabilityModel.h>

#include <OpenMS/CONCEPT/Constants.h>
#include <OpenMS/CONCEPT/LogStream.h>
#include <OpenMS/DATASTRUCTURES/String.h>
#include <OpenMS/DATASTRUCTURES/ListUtils.h>
#include <OpenMS/FORMAT/TextFile.h>
#include <OpenMS/MATH/STATISTICS/StatisticFunctions.h>
#include <OpenMS/METADATA/PeptideIdentification.h>
#include <OpenMS/METADATA/ProteinIdentification.h>
#include <OpenMS/METADATA/PeptideHit.h>

#include <QDir>

#include <boost/math/special_functions/fpclassify.hpp>

#include <algorithm>

using namespace std;

namespace OpenMS
{
  namespace Math
  {
    PosteriorErrorProbabilityModel::PosteriorErrorProbabilityModel() :
      DefaultParamHandler("PosteriorErrorProbabilityModel"),
      incorrectly_assigned_fit_param_(GaussFitter::GaussFitResult(-1, -1, -1)),
      correctly_assigned_fit_param_(GaussFitter::GaussFitResult(-1, -1, -1)),
      negative_prior_(0.5), max_incorrectly_(0), max_correctly_(0), smallest_score_(0)
    {
      defaults_.setValue("out_plot", "", "If given, the some output files will be saved in the following manner: <out_plot>_scores.txt for the scores and <out_plot> which contains the fitted values for each step of the EM-algorithm, e.g., out_plot = /usr/home/OMSSA123 leads to /usr/home/OMSSA123_scores.txt, /usr/home/OMSSA123 will be written. If no directory is specified, e.g. instead of '/usr/home/OMSSA123' just OMSSA123, the files will be written into the working directory.", ListUtils::create<String>("advanced,output file"));
      defaults_.setValue("number_of_bins", 100, "Number of bins used for visualization. Only needed if each iteration step of the EM-Algorithm will be visualized", ListUtils::create<String>("advanced"));
      defaults_.setValue("incorrectly_assigned", "Gumbel", "for 'Gumbel', the Gumbel distribution is used to plot incorrectly assigned sequences. For 'Gauss', the Gauss distribution is used.", ListUtils::create<String>("advanced"));
      defaults_.setValue("max_nr_iterations", 1000, "Bounds the number of iterations for the EM algorithm when convergence is slow.", ListUtils::create<String>("advanced"));
      defaults_.setValidStrings("incorrectly_assigned", ListUtils::create<String>("Gumbel,Gauss"));
      defaultsToParam_();
      getNegativeGnuplotFormula_ = &PosteriorErrorProbabilityModel::getGumbelGnuplotFormula;
      getPositiveGnuplotFormula_ = &PosteriorErrorProbabilityModel::getGaussGnuplotFormula;
    }

    PosteriorErrorProbabilityModel::~PosteriorErrorProbabilityModel()
    {
    }

    bool PosteriorErrorProbabilityModel::fit(std::vector<double>& search_engine_scores)
    {
      // nothing to fit?
      if (search_engine_scores.empty()) { return false; }

      //-------------------------------------------------------------
      // Initializing Parameters
      //-------------------------------------------------------------
      sort(search_engine_scores.begin(), search_engine_scores.end());

      smallest_score_ = search_engine_scores[0];

      vector<double> x_scores{search_engine_scores};
      for (double & d : x_scores) { d += fabs(smallest_score_) + 0.001; }

      negative_prior_ = 0.7;
      if (param_.getValue("incorrectly_assigned") == "Gumbel")
      {
        incorrectly_assigned_fit_param_.x0 = Math::mean(x_scores.begin(), x_scores.begin() + ceil(0.5 * x_scores.size())) + x_scores[0];
        incorrectly_assigned_fit_param_.sigma = Math::sd(x_scores.begin(), x_scores.end(), incorrectly_assigned_fit_param_.x0);
        incorrectly_assigned_fit_param_.A = 1   / sqrt(2 * Constants::PI * pow(incorrectly_assigned_fit_param_.sigma, 2));
        //TODO: Currently, the fit is calculated using the Gauss.
        getNegativeGnuplotFormula_ = &PosteriorErrorProbabilityModel::getGumbelGnuplotFormula;
      }
      else
      {
        incorrectly_assigned_fit_param_.x0 = Math::mean(x_scores.begin(), x_scores.begin() + ceil(0.5 * x_scores.size())) + x_scores[0];
        incorrectly_assigned_fit_param_.sigma = Math::sd(x_scores.begin(), x_scores.end(), incorrectly_assigned_fit_param_.x0);
        incorrectly_assigned_fit_param_.A = 1   / sqrt(2 * Constants::PI * pow(incorrectly_assigned_fit_param_.sigma, 2));
        getNegativeGnuplotFormula_ = &PosteriorErrorProbabilityModel::getGaussGnuplotFormula;
      }
      getPositiveGnuplotFormula_ = &PosteriorErrorProbabilityModel::getGaussGnuplotFormula;

      Size x_score_start = std::min(x_scores.size() - 1, (Size) ceil(x_scores.size() * 0.7)); // if only one score is present, ceil(...) will yield 1, which is an invalid index
      correctly_assigned_fit_param_.x0 = Math::mean(x_scores.begin() + x_score_start, x_scores.end()) + x_scores[x_score_start]; //(gauss_scores.begin()->getX() + (gauss_scores.end()-1)->getX())/2;
      correctly_assigned_fit_param_.sigma = incorrectly_assigned_fit_param_.sigma;
      correctly_assigned_fit_param_.A = 1.0   / sqrt(2 * Constants::PI * pow(correctly_assigned_fit_param_.sigma, 2));

      vector<double> incorrect_density, correct_density;
      fillDensities(x_scores, incorrect_density, correct_density);

      double maxlike = computeMaxLikelihood(incorrect_density, correct_density);

      //-------------------------------------------------------------
      // create files for output
      //-------------------------------------------------------------
      bool output_plots  = (param_.getValue("out_plot").toString().trim().length() > 0);
      TextFile file;
      if (output_plots)
      {
        // create output directory (if not already present)
        QDir dir(param_.getValue("out_plot").toString().toQString());
        if (!dir.cdUp())
        {
          LOG_ERROR << "Could not navigate to output directory for plots from '" << String(dir.dirName()) << "'." << std::endl;
          return false;
        }
        if (!dir.exists() && !dir.mkpath("."))
        {
          LOG_ERROR << "Could not create output directory for plots '" << String(dir.dirName()) << "'." << std::endl;
          return false;
        }
        //
        file = initPlots(x_scores);
      }

      //-------------------------------------------------------------
      // Estimate Parameters - EM algorithm
      //-------------------------------------------------------------
      bool stop_em_init = false;
      Int max_itns = param_.getValue("max_nr_iterations");
      int delta = 6;
      int itns = 0;

      do
      {
        //-------------------------------------------------------------
        // E-STEP
        double one_minus_sum_posterior = one_minus_sum_post(incorrect_density, correct_density);
        double sum_posterior = sum_post(incorrect_density, correct_density);

        // new mean
        double sum_positive_x0 = sum_pos_x0(x_scores, incorrect_density, correct_density);
        double sum_negative_x0 = sum_neg_x0(x_scores, incorrect_density, correct_density);

        double positive_mean = sum_positive_x0 / one_minus_sum_posterior;
        double negative_mean = sum_negative_x0 / sum_posterior;

        //i new standard deviation
        double sum_positive_sigma = sum_pos_sigma(x_scores, incorrect_density, correct_density, positive_mean);
        double sum_negative_sigma = sum_neg_sigma(x_scores, incorrect_density, correct_density, negative_mean);

        // update parameters
        correctly_assigned_fit_param_.x0 = positive_mean;
        if (sum_positive_sigma  != 0)
        {
          correctly_assigned_fit_param_.sigma = sqrt(sum_positive_sigma / one_minus_sum_posterior);
          correctly_assigned_fit_param_.A = 1 / sqrt(2 * Constants::PI * pow(correctly_assigned_fit_param_.sigma, 2));
        }

        incorrectly_assigned_fit_param_.x0 = negative_mean;
        if (sum_negative_sigma  != 0)
        {
          incorrectly_assigned_fit_param_.sigma = sqrt(sum_negative_sigma / sum_posterior);
          incorrectly_assigned_fit_param_.A = 1 / sqrt(2 * Constants::PI * pow(incorrectly_assigned_fit_param_.sigma, 2));
        }

        // compute new prior probabilities negative peptides
        fillDensities(x_scores, incorrect_density, correct_density);
        sum_posterior = sum_post(incorrect_density, correct_density);
        negative_prior_ = sum_posterior / x_scores.size();

        double new_maxlike(computeMaxLikelihood(incorrect_density, correct_density));
        if (boost::math::isnan(new_maxlike - maxlike)
          || new_maxlike < maxlike)
        {
          return false;
        }

        // check termination criterium
        if ((new_maxlike - maxlike) < pow(10.0, -delta) || itns >= max_itns)
        {
          if (itns >= max_itns)
          {
            LOG_WARN << "Number of iterations exceeded. Convergence criterion not met. Last likelihood increase: " << (new_maxlike - maxlike) << endl;
            LOG_WARN << "Algorithm returns probabilites for suboptimal fit. You might want to try raising the max. number of iterations and have a look at the distribution." << endl;
          }
          stop_em_init = true;
          sum_posterior = sum_post(incorrect_density, correct_density);
          negative_prior_ = sum_posterior / x_scores.size();

        }

        if (output_plots)
        {
          String formula1, formula2, formula3;
          formula1 = ((this)->*(getNegativeGnuplotFormula_))(incorrectly_assigned_fit_param_) + "* " + String(negative_prior_); //String(incorrectly_assigned_fit_param_.A) +" * exp(-(x - " + String(incorrectly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(incorrectly_assigned_fit_param_.sigma) + ") ** 2)"+ "*" + String(negative_prior_);
          formula2 = ((this)->*(getPositiveGnuplotFormula_))(correctly_assigned_fit_param_) + "* (1 - " + String(negative_prior_) + ")"; //String(correctly_assigned_fit_param_.A) +" * exp(-(x - " + String(correctly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(correctly_assigned_fit_param_.sigma) + ") ** 2)"+ "* (1 - " + String(negative_prior_) + ")";
          formula3 = getBothGnuplotFormula(incorrectly_assigned_fit_param_, correctly_assigned_fit_param_);
          // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
          file.addLine("plot '" + (String)param_.getValue("out_plot") + "_scores.txt' with boxes, " + formula1 + " , " + formula2 + " , " + formula3);
        }
        //update maximum likelihood
        maxlike = new_maxlike;
        ++itns;
      } while (!stop_em_init);

      //-------------------------------------------------------------
      // Finished fitting
      //-------------------------------------------------------------
      if (param_.getValue("incorrectly_assigned") == "Gumbel")
      {
        max_incorrectly_ = getGumbel_(incorrectly_assigned_fit_param_.x0, incorrectly_assigned_fit_param_);
      }
      else
      {
        max_incorrectly_ = incorrectly_assigned_fit_param_.eval(incorrectly_assigned_fit_param_.x0);
      }
      max_correctly_ = correctly_assigned_fit_param_.eval(correctly_assigned_fit_param_.x0);

      if (output_plots)
      {
        String formula1 = ((this)->*(getNegativeGnuplotFormula_))(incorrectly_assigned_fit_param_) + "*" + String(negative_prior_); //String(incorrectly_assigned_fit_param_.A) +" * exp(-(x - " + String(incorrectly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(incorrectly_assigned_fit_param_.sigma) + ") ** 2)"+ "*" + String(negative_prior_);
        String formula2 = ((this)->*(getPositiveGnuplotFormula_))(correctly_assigned_fit_param_) + "* (1 - " + String(negative_prior_) + ")"; // String(correctly_assigned_fit_param_.A) +" * exp(-(x - " + String(correctly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(correctly_assigned_fit_param_.sigma) + ") ** 2)"+ "* (1 - " + String(negative_prior_) + ")";
        String formula3 = getBothGnuplotFormula(incorrectly_assigned_fit_param_, correctly_assigned_fit_param_);
        // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
        file.addLine("plot '" + (String)param_.getValue("out_plot") + "_scores.txt' with boxes, " + formula1 + " , " + formula2 + " , " + formula3);
        file.store((String)param_.getValue("out_plot"));
        tryGnuplot((String)param_.getValue("out_plot"));
      }
      return true;
    }

    bool PosteriorErrorProbabilityModel::fit(std::vector<double>& search_engine_scores, vector<double>& probabilities)
    {
      bool return_value = fit(search_engine_scores);

      if (!return_value) return false;

      probabilities = std::vector<double>(search_engine_scores);
      for (double & p : probabilities) { p = computeProbability(p); }

      return true;
    }

    void PosteriorErrorProbabilityModel::fillDensities(vector<double>& x_scores, vector<double>& incorrect_density, vector<double>& correct_density)
    {
      if (incorrect_density.size() != x_scores.size())
      {
        incorrect_density.resize(x_scores.size());
        correct_density.resize(x_scores.size());
      }
      vector<double>::iterator incorrect(incorrect_density.begin());
      vector<double>::iterator correct(correct_density.begin());
      for (double const & score : x_scores)
      {
        // TODO: incorrect is currently filled with gauss as fitting gumble is not supported
        *incorrect = incorrectly_assigned_fit_param_.eval(score);
        *correct = correctly_assigned_fit_param_.eval(score);
        ++incorrect;
        ++correct;
      }
    }

    double PosteriorErrorProbabilityModel::computeMaxLikelihood(vector<double>& incorrect_density, vector<double>& correct_density)
    {
      double maxlike(0);
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect)
      {
        maxlike += log10(negative_prior_ * (*incorrect) + (1 - negative_prior_) * (*correct));
      }
      return maxlike;
    }

    double PosteriorErrorProbabilityModel::one_minus_sum_post(vector<double>& incorrect_density, vector<double>& correct_density)
    {
      double one_min(0);
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect)
      {
        one_min +=  1  - ((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)));
      }
      return one_min;
    }

    double PosteriorErrorProbabilityModel::sum_post(vector<double>& incorrect_density, vector<double>& correct_density)
    {
      double post(0);
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect)
      {
        post += ((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)));
      }
      return post;
    }

    double PosteriorErrorProbabilityModel::sum_pos_x0(vector<double>& x_scores, vector<double>& incorrect_density, vector<double>& correct_density)
    {
      double pos_x0(0);
      vector<double>::iterator the_x = x_scores.begin();
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect, ++the_x)
      {
        pos_x0 += ((1  - ((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)))) * (*the_x));
      }
      return pos_x0;
    }

    double PosteriorErrorProbabilityModel::sum_neg_x0(vector<double>& x_scores, vector<double>& incorrect_density, vector<double>& correct_density)
    {
      double neg_x0(0);
      vector<double>::iterator the_x = x_scores.begin();
      vector<double>::iterator correct = correct_density.begin();
      for (vector<double>::iterator incorrect = incorrect_density.begin(); incorrect < incorrect_density.end(); ++correct, ++incorrect, ++the_x)
      {
        neg_x0 += ((((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)))) * (*the_x));
      }
      return neg_x0;
    }

    double PosteriorErrorProbabilityModel::sum_pos_sigma(vector<double>& x_scores, vector<double>& incorrect_density, vector<double>& correct_density, double positive_mean)
    {
      double pos_sigma(0);
      vector<double>::iterator the_x = x_scores.begin();
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect, ++the_x)
      {
        pos_sigma += ((1  - ((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)))) * pow((*the_x) - positive_mean, 2));
      }
      return pos_sigma;
    }

    double PosteriorErrorProbabilityModel::sum_neg_sigma(vector<double>& x_scores, vector<double>& incorrect_density, vector<double>& correct_density, double positive_mean)
    {
      double neg_sigma(0);
      vector<double>::iterator the_x = x_scores.begin();
      vector<double>::iterator incorrect = incorrect_density.begin();
      for (vector<double>::iterator correct = correct_density.begin(); correct < correct_density.end(); ++correct, ++incorrect, ++the_x)
      {
        neg_sigma += ((((negative_prior_ * (*incorrect)) / ((negative_prior_ * (*incorrect)) + (1 - negative_prior_) * (*correct)))) * pow((*the_x) - positive_mean, 2));
      }
      return neg_sigma;
    }

    double PosteriorErrorProbabilityModel::computeProbability(double score) const
    {
      score = score + fabs(smallest_score_) + 0.001;
      double x_neg, x_pos;

      // the score is smaller than the peak of incorrectly assigned sequences. To ensure that the probabilities wont rise again use the incorrectly assigned peak for computation
      if (score < incorrectly_assigned_fit_param_.x0)
      {
        x_neg = max_incorrectly_;
        x_pos = correctly_assigned_fit_param_.eval(score);
      }
      // same as above. However, this time to ensure that probabilities wont drop again.
      else if (score > correctly_assigned_fit_param_.x0)
      {
        x_neg = getGumbel_(score, incorrectly_assigned_fit_param_);
        x_pos = max_correctly_;
      }
      // if its in between use the normal formula
      else
      {
        x_neg = getGumbel_(score, incorrectly_assigned_fit_param_);
        x_pos = correctly_assigned_fit_param_.eval(score);
      }
      return (negative_prior_ * x_neg) / ((negative_prior_ * x_neg) + (1 - negative_prior_) * x_pos);
    }

    TextFile PosteriorErrorProbabilityModel::initPlots(vector<double>& x_scores)
    {
      std::vector<DPosition<2> > points;
      Int number_of_bins = param_.getValue("number_of_bins");
      points.resize(number_of_bins);
      DPosition<2> temp;
      double dividing_score = (x_scores.back() - x_scores[0]) / number_of_bins;

      temp.setX(dividing_score / 2);
      temp.setY(0);
      Int bin = 0;
      points[bin] = temp;
      double temp_divider = dividing_score;
      for (std::vector<double>::iterator it = x_scores.begin(); it < x_scores.end(); ++it)
      {
        if (temp_divider - *it >= 0 && bin < number_of_bins - 1)
        {
          points[bin].setY(points[bin].getY() + 1);
        }
        else if (bin  == number_of_bins - 1)
        {
          points[bin].setY(points[bin].getY() + 1);
        }
        else
        {
          temp.setX((temp_divider + temp_divider + dividing_score) / 2);
          temp.setY(1);
          ++bin;
          points[bin] = temp;
          temp_divider += dividing_score;
        }
      }

      TextFile data_points;
      for (vector<DPosition<2> >::iterator it = points.begin(); it < points.end(); ++it)
      {
        it->setY(it->getY() / (x_scores.size()  * dividing_score));
        data_points << (String(it->getX()) + "\t" + it->getY());
      }
      data_points.store((String)param_.getValue("out_plot") + "_scores.txt");

      TextFile file;
      file << "set terminal pdf color solid linewidth 2.0 rounded";
      //file<<"set style empty solid 0.5 border -1";
      //file<<"set style function lines";
      file << "set xlabel \"discriminant score\"";
      file << "set ylabel \"density\"";
      //TODO: file<<"set title ";
      file << "set key off";
      // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
      file <<  "set output '" + (String)param_.getValue("out_plot") + ".pdf'";
      String formula1 = ((this)->*(getNegativeGnuplotFormula_))(incorrectly_assigned_fit_param_) + "* " + String(negative_prior_); //String(incorrectly_assigned_fit_param_.A) +" * exp(-(x - " + String(incorrectly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(incorrectly_assigned_fit_param_.sigma) + ") ** 2)"+ "*" + String(negative_prior_);
      String formula2 = ((this)->*(getPositiveGnuplotFormula_))(correctly_assigned_fit_param_) + "* (1 - " + String(negative_prior_) + ")"; //String(correctly_assigned_fit_param_.A) +" * exp(-(x - " + String(correctly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(correctly_assigned_fit_param_.sigma) + ") ** 2)"+ "* (1 - " + String(negative_prior_) + ")";
      // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
      file << ("plot '" + (String)param_.getValue("out_plot") + "_scores.txt' with boxes, " + formula1 + " , " + formula2);
      return file;
    }

    const String PosteriorErrorProbabilityModel::getGumbelGnuplotFormula(const GaussFitter::GaussFitResult& params) const
    {
      // build a formula with the fitted parameters for gnuplot
      stringstream formula;
      formula << "(1/" << params.sigma << ") * " << "exp(( " << params.x0 << "- x)/" << params.sigma << ") * exp(-exp((" << params.x0 << " - x)/" << params.sigma << "))";
      return formula.str();
    }

    const String PosteriorErrorProbabilityModel::getGaussGnuplotFormula(const GaussFitter::GaussFitResult& params) const
    {
      stringstream formula;
      formula << params.A << " * exp(-(x - " << params.x0 << ") ** 2 / 2 / (" << params.sigma << ") ** 2)";
      return formula.str();
    }

    const String PosteriorErrorProbabilityModel::getBothGnuplotFormula(const GaussFitter::GaussFitResult& incorrect, const GaussFitter::GaussFitResult& correct) const
    {
      stringstream formula;
      formula << negative_prior_ << "*" <<  ((this)->*(getNegativeGnuplotFormula_))(incorrect) << " + (1-" << negative_prior_ << ")*" << ((this)->*(getPositiveGnuplotFormula_))(correct);
      return formula.str();
    }

    void PosteriorErrorProbabilityModel::plotTargetDecoyEstimation(vector<double>& target, vector<double>& decoy)
    {
      if (target.size() == 0 || decoy.size() == 0)
      {
        StringList empty;
        if (target.size() == 0) empty.push_back("target");
        if (decoy.size() == 0) empty.push_back("decoy");
        LOG_WARN << "Target-Decoy plot was called, but '" << ListUtils::concatenate(empty, "' and '") << "' has no data! Unable to create a target-decoy plot." << std::endl;
        return;
      }
      Int number_of_bins = param_.getValue("number_of_bins");
      std::vector<DPosition<3> > points(number_of_bins);
      DPosition<3> temp;

      sort(target.begin(), target.end());
      sort(decoy.begin(), decoy.end());

      double dividing_score = (max(target.back(), decoy.back()) /*scores.back()*/ - min(target[0], decoy[0]) /*scores[0]*/) / number_of_bins;

      temp[0] = (dividing_score / 2);
      temp[1] = 0;
      temp[2] = 0;
      Int bin = 0;
      points[bin] = temp;
      double temp_divider = dividing_score;
      for (std::vector<double>::iterator it = target.begin(); it < target.end(); ++it)
      {
        *it = *it + fabs(smallest_score_) + 0.001;
        if (temp_divider - *it >= 0 && bin < number_of_bins - 1)
        {
          points[bin][1] += 1;
        }
        else if (bin  == number_of_bins - 1)
        {
          points[bin][1] += 1;
        }
        else
        {
          temp[0] = ((temp_divider + temp_divider + dividing_score) / 2);
          temp[1] = 1;
          ++bin;
          points[bin] = temp;
          temp_divider += dividing_score;
        }
      }

      bin = 0;
      temp_divider = dividing_score;
      for (std::vector<double>::iterator it = decoy.begin(); it < decoy.end(); ++it)
      {
        *it = *it + fabs(smallest_score_) + 0.001;
        if (temp_divider - *it >= 0 && bin < number_of_bins - 1)
        {
          points[bin][2] += 1;
        }
        else if (bin  == number_of_bins - 1)
        {
          points[bin][2] += 1;
        }
        else
        {
          // temp[0] = ((temp_divider + temp_divider + dividing_score)/2);
          // temp[2] = 1;
          ++bin;
          points[bin][2] = 1;
          temp_divider += dividing_score;
        }
      }

      TextFile data_points;
      for (vector<DPosition<3> >::iterator it = points.begin(); it < points.end(); ++it)
      {
        (*it)[1] = ((*it)[1] / ((decoy.size() + target.size())  * dividing_score));
        (*it)[2] = ((*it)[2] / ((decoy.size() + target.size())  * dividing_score));
        String temp_ = (*it)[0];
        temp_ += "\t";
        temp_ += (*it)[1];
        temp_ += "\t";
        temp_ += (*it)[2];
        data_points << temp_;
      }
      data_points.store((String)param_.getValue("out_plot") + "_target_decoy_scores.txt");
      TextFile file;
      file << "set terminal pdf color solid linewidth 2.0 rounded";
      //file<<"set style empty solid 0.5 border -1";
      //file<<"set style function lines";
      file << "set xlabel \"discriminant score\"";
      file << "set ylabel \"density\"";
      //TODO: file<<"set title ";
      file << "set key off";
      // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
      file << String("set output '") +  (String)param_.getValue("out_plot") + "_target_decoy.pdf'";
      String formula1, formula2;
      formula1 = getGumbelGnuplotFormula(getIncorrectlyAssignedFitResult()) + "* " + String(getNegativePrior()); //String(incorrectly_assigned_fit_param_.A) +" * exp(-(x - " + String(incorrectly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(incorrectly_assigned_fit_param_.sigma) + ") ** 2)"+ "*" + String(negative_prior_);
      formula2 = getGaussGnuplotFormula(getCorrectlyAssignedFitResult()) + "* (1 - " + String(getNegativePrior()) + ")"; //String(correctly_assigned_fit_param_.A) +" * exp(-(x - " + String(correctly_assigned_fit_param_.x0) + ") ** 2 / 2 / (" + String(correctly_assigned_fit_param_.sigma) + ") ** 2)"+ "* (1 - " + String(negative_prior_) + ")";
      // important: use single quotes for paths, since otherwise backslashes will not be accepted on Windows!
      file << ("plot '" + (String)param_.getValue("out_plot") + "_target_decoy_scores.txt'   using 1:3  with boxes fill solid 0.8 noborder, \"" + (String)param_.getValue("out_plot") + "_target_decoy_scores.txt\"  using 1:2  with boxes, " + formula1 + " , " + formula2);
      file.store((String)param_.getValue("out_plot") + "_target_decoy");
      tryGnuplot((String)param_.getValue("out_plot") + "_target_decoy");
    }

    void PosteriorErrorProbabilityModel::tryGnuplot(const String& gp_file)
    {
      LOG_INFO << "Attempting to call 'gnuplot' ...";
      String cmd = String("gnuplot \"") + gp_file + "\"";
      if (system(cmd.c_str()))  // 0 is success!
      {
        LOG_WARN << "Calling 'gnuplot' on '" << gp_file << "' failed. Please create plots manually." << std::endl;
      }
      else LOG_INFO << " success!" << std::endl;

    }

    double PosteriorErrorProbabilityModel::transformScore_(const String & engine, const PeptideHit & hit)
    {
      // Set fixed e-value threshold
      const double smallest_e_value_ = numeric_limits<double>::denorm_min();

      if (engine == "OMSSA")
      {
        return (-1) * log10(max(hit.getScore(), smallest_e_value_));
      }
      else if (engine == "MYRIMATCH" )
      {
        return hit.getScore();
      }
      else if (engine == "XTANDEM")
      {
        return (-1) * log10(max((double)hit.getMetaValue("E-Value"), smallest_e_value_));
      }
      else if (engine == "MASCOT")
      {
        // issue #740: unable to fit data with score 0
        if (hit.getScore() == 0.0)
        {
          return numeric_limits<double>::quiet_NaN();
        }
        // end issue #740
        if (hit.metaValueExists("EValue"))
        {
          return (-1) * log10(max((double)hit.getMetaValue("EValue"), smallest_e_value_));
        }
        if (hit.metaValueExists("expect"))
        {
          return (-1) * log10(max((double)hit.getMetaValue("expect"), smallest_e_value_));
        }
      }
      else if (engine == "SPECTRAST")
      {
        return 100 * hit.getScore(); // f-val
      }
      else if (engine == "SIMTANDEM")
      {
        if (hit.metaValueExists("E-Value"))
        {
          return (-1) * log10(max((double)hit.getMetaValue("E-Value"), smallest_e_value_));
        }
      }
      else if ((engine == "MSGFPLUS") || (engine == "MS-GF+"))
      {
        if (hit.metaValueExists("MS:1002053"))  // name: MS-GF:EValue
        {
          return (-1) * log10(max((double)hit.getMetaValue("MS:1002053"), smallest_e_value_));
        }
        else if (hit.metaValueExists("expect"))
        {
          return (-1) * log10(max((double)hit.getMetaValue("expect"), smallest_e_value_));
        }
      }
      else if (engine == "COMET")
      {
        if (hit.metaValueExists("MS:1002257")) // name: Comet:expectation value
        {
          return (-1) * log10(max((double)hit.getMetaValue("MS:1002257"), smallest_e_value_));
        }
        else if (hit.metaValueExists("expect"))
        {
          return (-1) * log10(max((double)hit.getMetaValue("expect"), smallest_e_value_));
        }
      }

      throw Exception::UnableToFit(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, "No parameters for chosen search engine", "The chosen search engine is currently not supported");
    }

    map<String, vector<vector<double>>> PosteriorErrorProbabilityModel::extractAndTransformScores(
      const vector<ProteinIdentification> & protein_ids,
      const vector<PeptideIdentification> & peptide_ids,
      const bool split_charge,
      const bool top_hits_only,
      const bool target_decoy_available,
      const double fdr_for_targets_smaller)
    {
      std::set<Int> charges;
      const StringList search_engines = ListUtils::create<String>("XTandem,OMSSA,MASCOT,SpectraST,MyriMatch,SimTandem,MSGFPlus,MS-GF+,Comet");

      if (split_charge)
      {  // determine different charges in data
        for (PeptideIdentification const & pep_id : peptide_ids)
        {
          const vector<PeptideHit>& hits = pep_id.getHits();
          for (PeptideHit const & hit : hits) { charges.insert(hit.getCharge()); }
        }
        if (charges.empty())
        {
          throw Exception::Precondition(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, "'split_charge' is set, but the list of charge states is empty");
        }
      }

      set<Int>::iterator charge_it = charges.begin(); // charges can be empty, no problem if split_charge is not set
      map<String, vector<vector<double> > > all_scores;
      char splitter = ','; // to split the engine from the charge state later on
      do
      {
        vector<double> scores, decoy, target;
        for (String supported_engine : search_engines)
        {
          supported_engine.toUpper();
          for (ProteinIdentification const & prot : protein_ids)
          {
            String search_engine = prot.getSearchEngine();
            search_engine.toUpper();

            if (supported_engine == search_engine)
            {
              for (PeptideIdentification pep : peptide_ids)
              {
                // make sure we are comparing peptide and proteins of the same search run
                if (prot.getIdentifier() == pep.getIdentifier())
                {
                  pep.sort();
                  vector<PeptideHit>& hits = pep.getHits();
                  if (top_hits_only)
                  {
                    if (!hits.empty() && (!split_charge || hits[0].getCharge() == *charge_it))
                    {
                      double score = PosteriorErrorProbabilityModel::transformScore_(supported_engine, hits[0]);
                      if (!boost::math::isnan(score)) // issue #740: ignore scores with 0 values, otherwise you will get the error "unable to fit data"
                      {
                        scores.push_back(score);

                        if (target_decoy_available)
                        {
                          if (hits[0].getScore() < fdr_for_targets_smaller)
                          {
                            target.push_back(score);
                          }
                          else
                          {
                            decoy.push_back(score);
                          }
                        }
                      }
                    }
                  }
                  else
                  {
                    for (PeptideHit const & hit : hits)
                    {
                      if (!split_charge || (hit.getCharge() == *charge_it))
                      {
                        double score = PosteriorErrorProbabilityModel::transformScore_(supported_engine, hit);
                        if (!boost::math::isnan(score)) // issue #740: ignore scores with 0 values, otherwise you will get the error "unable to fit data"
                        {
                          scores.push_back(score);
                        }
                      }
                    }
                  }
                }
              }
            }
          }

          if (scores.size() > 2)
          {
            vector<vector<double> > tmp;
            tmp.push_back(scores);
            tmp.push_back(target);
            tmp.push_back(decoy);

            if (split_charge)
            {
              String engine_with_charge_state = supported_engine + String(splitter) + String(*charge_it);
              all_scores.insert(make_pair(engine_with_charge_state, tmp));
            }
            else
            {
              all_scores.insert(make_pair(supported_engine, tmp));
            }
          }

          scores.clear();
          target.clear();
          decoy.clear();
        }

        if (split_charge) { ++charge_it; }
      } while (charge_it != charges.end());
      return all_scores;
    }

    void PosteriorErrorProbabilityModel::updateScores(
      const PosteriorErrorProbabilityModel & PEP_model,
      const String & search_engine,
      const Int charge,
      const bool prob_correct,
      const bool split_charge,
      vector<ProteinIdentification> & protein_ids,
      vector<PeptideIdentification> & peptide_ids,
      bool & unable_to_fit_data,
      bool & data_might_not_be_well_fit)
    {
      String engine(search_engine);
      unable_to_fit_data = true;
      data_might_not_be_well_fit = true;

      engine.toUpper();
      for (ProteinIdentification & prot : protein_ids)
      {
        String search_engine = prot.getSearchEngine();
        search_engine.toUpper();

        if (engine == search_engine)
        {
          for (PeptideIdentification & pep : peptide_ids)
          {
            if (prot.getIdentifier() == pep.getIdentifier())
            {
              String score_type = pep.getScoreType() + "_score";
              vector<PeptideHit> hits = pep.getHits();
              for (PeptideHit & hit : hits)
              {
                if (!split_charge || (hit.getCharge() == charge))
                {
                  double score;
                  hit.setMetaValue(score_type, hit.getScore());
                  score = PosteriorErrorProbabilityModel::transformScore_(engine, hit);

                  if (boost::math::isnan(score)) // issue #740: ignore scores with 0 values, otherwise you will get the error "unable to fit data"
                  {
                    score = 1.0;
                  }
                  else
                  {
                    score = PEP_model.computeProbability(score);

                    // invalid score? invalid fit!
                    if ((score > 0.0) && (score < 1.0)) unable_to_fit_data = false;
                    if ((score > 0.2) && (score < 0.8)) data_might_not_be_well_fit = false;
                  }
                  hit.setScore(score);
                  if (prob_correct)
                  {
                    hit.setScore(1.0 - score);
                  }
                  else
                  {
                    hit.setScore(score);
                  }
                }
              }
              pep.setHits(hits);
            }
            if (prob_correct)
            {
              pep.setScoreType("Posterior Probability");
              pep.setHigherScoreBetter(true);
            }
            else
            {
              pep.setScoreType("Posterior Error Probability");
              pep.setHigherScoreBetter(false);
            }
          }
        }
      }
    }
  } // namespace Math
} // namespace OpenMS


/**
 * @file teaset.cc
 *
 * Copyright (c) 2013 Leon Lynch
 *
 * This file is licensed under the terms of the MIT license.
 * See LICENSE file.
 */

#include "teaset.h"

#include <cstddef>
#include <cstdio>

#include <sstream>
#include <string>
#include <array>
#include <vector>

#include <boost/lexical_cast.hpp>

extern const char teapot_geometry_str[];
extern const char teacup_geometry_str[];
extern const char teaspoon_geometry_str[];

Teaset::~Teaset()
{
}

static std::size_t readCount(std::istringstream& ss)
{
	std::string str;
	while (str.empty())
		std::getline(ss, str);

	return boost::lexical_cast<std::size_t>(str);
}

void Teaset::readData(const char* data, bool data_is_ccw)
{
	std::istringstream ss(data);
	std::size_t count;
	std::vector<std::array<unsigned int,16>> indices;
	std::vector<glm::vec3> vertices;

	// read indices
	count = readCount(ss);
	indices.resize(count);
	for (std::size_t i = 0; i < count; ++i) {
		std::string index_str;
		std::getline(ss, index_str);

		std::sscanf(
			index_str.c_str(),
			"%u,%u,%u,%u,"
			"%u,%u,%u,%u,"
			"%u,%u,%u,%u,"
			"%u,%u,%u,%u",
			&indices[i][0], &indices[i][1], &indices[i][2], &indices[i][3],
			&indices[i][4], &indices[i][5], &indices[i][6], &indices[i][7],
			&indices[i][8], &indices[i][9], &indices[i][10], &indices[i][11],
			&indices[i][12], &indices[i][13], &indices[i][14], &indices[i][15]
		);
	}

	// read vertices
	count = readCount(ss);
	vertices.resize(count);
	for (std::size_t i = 0; i < count; ++i) {
		std::string vertex_str;
		std::getline(ss, vertex_str);

		std::sscanf(
			vertex_str.c_str(),
			"%f,%f,%f",
			&vertices[i][0], &vertices[i][1], &vertices[i][2]
		);
	}

	// lookup control points
	for (auto&& index : indices) {
		BezierPatch patch;

		if (data_is_ccw) {
			for (std::size_t i = 0; i < 16; ++i) {
				patch.k[i/4][i%4] = vertices[index[i] - 1];
			}
		} else {
			for (std::size_t i = 0; i < 16; ++i) {
				patch.k[i/4][3 - (i%4)] = vertices[index[i] - 1];
			}
		}

		patches.push_back(patch);
	}
}

Teapot::Teapot()
{
	readData(teapot_geometry_str, false);
}

Teacup::Teacup()
{
	readData(teacup_geometry_str, true);
}

Teaspoon::Teaspoon()
{
	readData(teaspoon_geometry_str, true);
}