/*
anharmonic_core.cpp

Copyright (c) 2014 Terumasa Tadano

This file is distributed under the terms of the MIT license.
Please see the file 'LICENCE.txt' in the root directory
or http://opensource.org/licenses/mit-license.php for information.
*/

#include "mpi_common.h"
#include "anharmonic_core.h"
#include "constants.h"
#include "dynamical.h"
#include "error.h"
#include "fcs_phonon.h"
#include "integration.h"
#include "kpoint.h"
#include "mathfunctions.h"
#include "memory.h"
#include "mode_analysis.h"
#include "phonon_dos.h"
#include "system.h"
#include "thermodynamics.h"
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <vector>

#ifdef _OPENMP

#include <omp.h>

#endif

using namespace PHON_NS;

AnharmonicCore::AnharmonicCore(PHON *phon) : Pointers(phon)
{
    set_default_variables();
}

AnharmonicCore::~AnharmonicCore()
{
    deallocate_variables();
};

void AnharmonicCore::set_default_variables()
{
    quartic_mode = 0;
    use_tuned_ver = true;
    use_triplet_symmetry = true;
    use_quartet_symmetry = true;
    relvec_v3 = nullptr;
    relvec_v4 = nullptr;
    invmass_v3 = nullptr;
    invmass_v4 = nullptr;
    evec_index_v3 = nullptr;
    evec_index_v4 = nullptr;
    fcs_group_v3 = nullptr;
    fcs_group_v4 = nullptr;
    phi3_reciprocal = nullptr;
    phi4_reciprocal = nullptr;
    phase_storage_dos = nullptr;
}

void AnharmonicCore::deallocate_variables()
{
    if (relvec_v3) {
        deallocate(relvec_v3);
    }
    if (relvec_v4) {
        deallocate(relvec_v4);
    }
    if (invmass_v3) {
        deallocate(invmass_v3);
    }
    if (invmass_v4) {
        deallocate(invmass_v4);
    }
    if (evec_index_v3) {
        deallocate(evec_index_v3);
    }
    if (evec_index_v4) {
        deallocate(evec_index_v4);
    }
    if (fcs_group_v3) {
        deallocate(fcs_group_v3);
    }
    if (fcs_group_v4) {
        deallocate(fcs_group_v4);
    }
    if (phi3_reciprocal) {
        deallocate(phi3_reciprocal);
    }
    if (phi4_reciprocal) {
        deallocate(phi4_reciprocal);
    }
    if (phase_storage_dos) delete phase_storage_dos;
}

void AnharmonicCore::setup()
{
    sym_permutation = true;
    use_tuned_ver = true;
    MPI_Bcast(&use_tuned_ver, 1, MPI_CXX_BOOL, 0, MPI_COMM_WORLD);

    if (fcs_phonon->maxorder >= 2) setup_cubic();
    if (fcs_phonon->maxorder >= 3) setup_quartic();

    if (!mode_analysis->calc_fstate_k && dos->kmesh_dos) {
        phase_storage_dos = new PhaseFactorStorage(dos->kmesh_dos->nk_i);
        phase_storage_dos->create(use_tuned_ver);
    }
}

void AnharmonicCore::prepare_relative_vector(const std::vector<FcsArrayWithCell> &fcs_in,
                                             const unsigned int N,
                                             const int number_of_groups,
                                             std::vector<double> *fcs_group,
                                             std::vector<RelativeVector> *&vec_out) const
{
    int i, j, k;

    double vecs[3][3];
    double **xshift_s;

    std::vector<unsigned int> atm_super, atm_prim;
    std::vector<unsigned int> cells;

    double mat_convert[3][3];

    for (i = 0; i < 3; ++i) {
        for (j = 0; j < 3; ++j) {
            mat_convert[i][j] = 0.0;
            for (k = 0; k < 3; ++k) {
                mat_convert[i][j] += system->rlavec_p[i][k] * system->lavec_s_anharm[k][j];
            }
        }
    }

    allocate(xshift_s, 27, 3);

    for (i = 0; i < 3; ++i) xshift_s[0][i] = 0.0;

    unsigned int icell = 0;

    for (int ix = -1; ix <= 1; ++ix) {
        for (int iy = -1; iy <= 1; ++iy) {
            for (int iz = -1; iz <= 1; ++iz) {
                if (ix == 0 && iy == 0 && iz == 0) continue;

                ++icell;

                xshift_s[icell][0] = static_cast<double>(ix);
                xshift_s[icell][1] = static_cast<double>(iy);
                xshift_s[icell][2] = static_cast<double>(iz);
            }
        }
    }

    atm_prim.resize(N);
    cells.resize(N);
    atm_super.resize(N);

    unsigned int icount = 0;

    for (auto igroup = 0; igroup < number_of_groups; ++igroup) {

        unsigned int nsize_group = fcs_group[igroup].size();

        for (j = 0; j < nsize_group; ++j) {

            for (i = 0; i < N; ++i) {
                atm_prim[i] = fcs_in[icount].pairs[i].index / 3;
                const auto tran_tmp = fcs_in[icount].pairs[i].tran;
                cells[i] = fcs_in[icount].pairs[i].cell_s;
                atm_super[i] = system->map_p2s_anharm[atm_prim[i]][tran_tmp];
            }

            for (i = 0; i < N - 1; ++i) {
                for (k = 0; k < 3; ++k) {
                    vecs[i][k] = system->xr_s_anharm[atm_super[i + 1]][k] + xshift_s[cells[i + 1]][k]
                                 - system->xr_s_anharm[system->map_p2s_anharm[atm_prim[i + 1]][0]][k];
                }
                rotvec(vecs[i], vecs[i], mat_convert);
            }

            if (N == 3) {
                vec_out[igroup].emplace_back(vecs[0], vecs[1]);
            } else if (N == 4) {
                vec_out[igroup].emplace_back(vecs[0], vecs[1], vecs[2]);
            }
            ++icount;
        }
    }

    deallocate(xshift_s);
}

void AnharmonicCore::prepare_group_of_force_constants(const std::vector<FcsArrayWithCell> &fcs_in,
                                                      const unsigned int N,
                                                      int &number_of_groups,
                                                      std::vector<double> *&fcs_group_out) const
{
    // Find the number of groups which has different evecs.

    unsigned int i;
    std::vector<int> arr_old, arr_tmp;

    number_of_groups = 0;

    arr_old.clear();
    for (i = 0; i < N; ++i) {
        arr_old.push_back(-1);
    }

    for (const auto &it: fcs_in) {

        arr_tmp.clear();

        for (i = 0; i < it.pairs.size(); ++i) {
            arr_tmp.push_back(it.pairs[i].index);
        }

        if (arr_tmp != arr_old) {
            ++number_of_groups;
            arr_old.clear();
            arr_old.reserve(arr_tmp.size());
            std::copy(arr_tmp.begin(), arr_tmp.end(), std::back_inserter(arr_old));
        }
    }

    allocate(fcs_group_out, number_of_groups);

    int igroup = -1;

    arr_old.clear();
    for (i = 0; i < N; ++i) {
        arr_old.push_back(-1);
    }

    for (const auto &it: fcs_in) {

        arr_tmp.clear();

        for (i = 0; i < it.pairs.size(); ++i) {
            arr_tmp.push_back(it.pairs[i].index);
        }

        if (arr_tmp != arr_old) {
            ++igroup;
            arr_old.clear();
            arr_old.reserve(arr_tmp.size());
            std::copy(arr_tmp.begin(), arr_tmp.end(), std::back_inserter(arr_old));
        }

        fcs_group_out[igroup].push_back(it.fcs_val);
    }
}

std::complex<double> AnharmonicCore::V3(const unsigned int ks[3])
{
    return V3(ks,
              dos->kmesh_dos->xk,
              dos->dymat_dos->get_eigenvalues(),
              dos->dymat_dos->get_eigenvectors(),
              this->phase_storage_dos);
}

std::complex<double> AnharmonicCore::V3(const unsigned int ks[3],
                                        const double *const *xk_in,
                                        const double *const *eval_in,
                                        const std::complex<double> *const *const *evec_in)
{
    return V3(ks,
              xk_in,
              eval_in,
              evec_in,
              this->phase_storage_dos);
}

std::complex<double> AnharmonicCore::V4(const unsigned int ks[4])
{
    return V4(ks,
              dos->kmesh_dos->xk,
              dos->dymat_dos->get_eigenvalues(),
              dos->dymat_dos->get_eigenvectors(),
              this->phase_storage_dos);
}

std::complex<double> AnharmonicCore::Phi3(const unsigned int ks[3])
{
    return Phi3(ks,
                dos->kmesh_dos->xk,
                dos->dymat_dos->get_eigenvalues(),
                dos->dymat_dos->get_eigenvectors(),
                this->phase_storage_dos);
}

std::complex<double> AnharmonicCore::Phi4(const unsigned int ks[4])
{
    return Phi4(ks,
                dos->kmesh_dos->xk,
                dos->dymat_dos->get_eigenvalues(),
                dos->dymat_dos->get_eigenvectors(),
                this->phase_storage_dos);
}

std::complex<double> AnharmonicCore::V3(const unsigned int ks[3],
                                        const double *const *xk_in,
                                        const double *const *eval_in,
                                        const std::complex<double> *const *const *evec_in,
                                        const PhaseFactorStorage *phase_storage_in)
{
    int i;
    unsigned int kn[3], sn[3];
    const int ns = dynamical->neval;

    double omega[3];
    auto ret = std::complex<double>(0.0, 0.0);
    auto ret_re = 0.0;
    auto ret_im = 0.0;

    for (i = 0; i < 3; ++i) {
        kn[i] = ks[i] / ns;
        sn[i] = ks[i] % ns;
        omega[i] = eval_in[kn[i]][sn[i]];
    }

    // Return zero if any of the involving phonon has imaginary frequency
    if (omega[0] < eps8 || omega[1] < eps8 || omega[2] < eps8) return 0.0;

//    for (i = 0; i < ngroup_v3; ++i) {
//        std::cout << "invmass_v3[i] = " << invmass_v3[i] << std::endl;
//    }
//
//    for (i = 0; i < ngroup_v3; ++i) {
//        std::cout << "evec_index_v3 = " << evec_index_v3[i][0]
//        << evec_index_v3[i][1] <<  evec_index_v3[i][2] << std::endl;
//    }

    if (kn[1] != kindex_phi3_stored[0] || kn[2] != kindex_phi3_stored[1]) {
        calc_phi3_reciprocal(xk_in[kn[1]],
                             xk_in[kn[2]],
                             phase_storage_in,
                             phi3_reciprocal);

//        for (i = 0; i < ngroup_v3; ++i) {
//            std::cout << phi3_reciprocal[i] << std::endl;
//        }
        kindex_phi3_stored[0] = kn[1];
        kindex_phi3_stored[1] = kn[2];
    }
#ifdef _OPENMP
#pragma omp parallel for private(ret), reduction(+: ret_re, ret_im)
#endif
    for (i = 0; i < ngroup_v3; ++i) {
        ret = evec_in[kn[0]][sn[0]][evec_index_v3[i][0]]
              * evec_in[kn[1]][sn[1]][evec_index_v3[i][1]]
              * evec_in[kn[2]][sn[2]][evec_index_v3[i][2]]
              * invmass_v3[i] * phi3_reciprocal[i];
        ret_re += ret.real();
        ret_im += ret.imag();
    }

    return std::complex<double>(ret_re, ret_im)
           / std::sqrt(omega[0] * omega[1] * omega[2]);
}

std::complex<double> AnharmonicCore::Phi3(const unsigned int ks[3],
                                          const double *const *xk_in,
                                          const double *const *eval_in,
                                          const std::complex<double> *const *const *evec_in,
                                          const PhaseFactorStorage *phase_storage_in)
{
    int i;
    unsigned int kn[3], sn[3];
    const auto ns = dynamical->neval;

    double omega[3];
    std::complex<double> ret = std::complex<double>(0.0, 0.0);
    double ret_re = 0.0;
    double ret_im = 0.0;

    for (i = 0; i < 3; ++i) {
        kn[i] = ks[i] / ns;
        sn[i] = ks[i] % ns;
        omega[i] = eval_in[kn[i]][sn[i]];
    }

    if (kn[1] != kindex_phi3_stored[0] || kn[2] != kindex_phi3_stored[1]) {
        calc_phi3_reciprocal(xk_in[kn[1]],
                             xk_in[kn[2]],
                             phase_storage_in,
                             phi3_reciprocal);
        kindex_phi3_stored[0] = kn[1];
        kindex_phi3_stored[1] = kn[2];
    }
#ifdef _OPENMP
#pragma omp parallel for private(ret), reduction(+: ret_re, ret_im)
#endif
    for (i = 0; i < ngroup_v3; ++i) {
        ret = evec_in[kn[0]][sn[0]][evec_index_v3[i][0]]
              * evec_in[kn[1]][sn[1]][evec_index_v3[i][1]]
              * evec_in[kn[2]][sn[2]][evec_index_v3[i][2]]
              * invmass_v3[i] * phi3_reciprocal[i];
        ret_re += ret.real();
        ret_im += ret.imag();
    }

    return std::complex<double>(ret_re, ret_im);
}

void AnharmonicCore::calc_phi3_reciprocal(const double *xk1,
                                          const double *xk2,
                                          const PhaseFactorStorage *phase_storage_in,
                                          std::complex<double> *ret)
{
    int i, j;
    double phase;
    std::complex<double> ret_in;
    unsigned int nsize_group;

    const auto tune_type_now = phase_storage_in->get_tune_type();

    if (tune_type_now == 1) {

#pragma omp parallel for private(ret_in, nsize_group, j, phase)
        for (i = 0; i < ngroup_v3; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v3[i].size();

            for (j = 0; j < nsize_group; ++j) {
                phase = relvec_v3[i][j].vecs[0][0] * xk1[0]
                        + relvec_v3[i][j].vecs[0][1] * xk1[1]
                        + relvec_v3[i][j].vecs[0][2] * xk1[2]
                        + relvec_v3[i][j].vecs[1][0] * xk2[0]
                        + relvec_v3[i][j].vecs[1][1] * xk2[1]
                        + relvec_v3[i][j].vecs[1][2] * xk2[2];

                ret_in += fcs_group_v3[i][j] * phase_storage_in->get_exp_type1(phase);
            }
            ret[i] = ret_in;
        }

    } else if (tune_type_now == 2) {

        // Tuned version is used when nk1=nk2=nk3 doesn't hold.

        double phase3[3];

#pragma omp parallel for private(ret_in, nsize_group, j, phase3)
        for (i = 0; i < ngroup_v3; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v3[i].size();

            for (j = 0; j < nsize_group; ++j) {
                for (auto ii = 0; ii < 3; ++ii) {
                    phase3[ii]
                            = relvec_v3[i][j].vecs[0][ii] * xk1[ii]
                              + relvec_v3[i][j].vecs[1][ii] * xk2[ii];
                }
                ret_in += fcs_group_v3[i][j] * phase_storage_in->get_exp_type2(phase3);
            }
            ret[i] = ret_in;
        }
    } else {
        // Original version
#pragma omp parallel for private(ret_in, nsize_group, phase, j)
        for (i = 0; i < ngroup_v3; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v3[i].size();

            for (j = 0; j < nsize_group; ++j) {
                phase
                        = relvec_v3[i][j].vecs[0][0] * xk1[0]
                          + relvec_v3[i][j].vecs[0][1] * xk1[1]
                          + relvec_v3[i][j].vecs[0][2] * xk1[2]
                          + relvec_v3[i][j].vecs[1][0] * xk2[0]
                          + relvec_v3[i][j].vecs[1][1] * xk2[1]
                          + relvec_v3[i][j].vecs[1][2] * xk2[2];
                ret_in += fcs_group_v3[i][j] * std::exp(im * phase);
            }
            ret[i] = ret_in;
        }
    }
}

std::complex<double> AnharmonicCore::V4(const unsigned int ks[4],
                                        const double *const *xk_in,
                                        const double *const *eval_in,
                                        const std::complex<double> *const *const *evec_in,
                                        const PhaseFactorStorage *phase_storage_in)
{
    int i;
    const int ns = dynamical->neval;
    unsigned int kn[4], sn[4];
    double omega[4];
    auto ret_re = 0.0;
    auto ret_im = 0.0;
    auto ret = std::complex<double>(0.0, 0.0);

    for (i = 0; i < 4; ++i) {
        kn[i] = ks[i] / ns;
        sn[i] = ks[i] % ns;
        omega[i] = eval_in[kn[i]][sn[i]];
    }
    // Return zero if any of the involving phonon has imaginary frequency
    if (omega[0] < eps8 || omega[1] < eps8 || omega[2] < eps8 || omega[3] < eps8) return 0.0;

    if (kn[1] != kindex_phi4_stored[0]
        || kn[2] != kindex_phi4_stored[1]
        || kn[3] != kindex_phi4_stored[2]) {

        calc_phi4_reciprocal(xk_in[kn[1]],
                             xk_in[kn[2]],
                             xk_in[kn[3]],
                             phase_storage_in,
                             phi4_reciprocal);

        kindex_phi4_stored[0] = kn[1];
        kindex_phi4_stored[1] = kn[2];
        kindex_phi4_stored[2] = kn[3];
    }

#ifdef _OPENMP
#pragma omp parallel for private(ret), reduction(+: ret_re, ret_im)
#endif
    for (i = 0; i < ngroup_v4; ++i) {
        ret = evec_in[kn[0]][sn[0]][evec_index_v4[i][0]]
              * evec_in[kn[1]][sn[1]][evec_index_v4[i][1]]
              * evec_in[kn[2]][sn[2]][evec_index_v4[i][2]]
              * evec_in[kn[3]][sn[3]][evec_index_v4[i][3]]
              * invmass_v4[i] * phi4_reciprocal[i];
        ret_re += ret.real();
        ret_im += ret.imag();
    }

    return std::complex<double>(ret_re, ret_im)
           / std::sqrt(omega[0] * omega[1] * omega[2] * omega[3]);
}

std::complex<double> AnharmonicCore::Phi4(const unsigned int ks[4],
                                          const double *const *xk_in,
                                          const double *const *eval_in,
                                          const std::complex<double> *const *const *evec_in,
                                          const PhaseFactorStorage *phase_storage_in)
{
    int i;
    int ns = dynamical->neval;
    unsigned int kn[4], sn[4];
    double omega[4];
    double ret_re = 0.0;
    double ret_im = 0.0;
    std::complex<double> ret = std::complex<double>(0.0, 0.0);

    for (i = 0; i < 4; ++i) {
        kn[i] = ks[i] / ns;
        sn[i] = ks[i] % ns;
        omega[i] = eval_in[kn[i]][sn[i]];
    }

    if (kn[1] != kindex_phi4_stored[0]
        || kn[2] != kindex_phi4_stored[1]
        || kn[3] != kindex_phi4_stored[2]) {

        calc_phi4_reciprocal(xk_in[kn[1]],
                             xk_in[kn[2]],
                             xk_in[kn[3]],
                             phase_storage_in,
                             phi4_reciprocal);

        kindex_phi4_stored[0] = kn[1];
        kindex_phi4_stored[1] = kn[2];
        kindex_phi4_stored[2] = kn[3];
    }

#ifdef _OPENMP
#pragma omp parallel for private(ret), reduction(+: ret_re, ret_im)
#endif
    for (i = 0; i < ngroup_v4; ++i) {
        ret = evec_in[kn[0]][sn[0]][evec_index_v4[i][0]]
              * evec_in[kn[1]][sn[1]][evec_index_v4[i][1]]
              * evec_in[kn[2]][sn[2]][evec_index_v4[i][2]]
              * evec_in[kn[3]][sn[3]][evec_index_v4[i][3]]
              * invmass_v4[i] * phi4_reciprocal[i];
        ret_re += ret.real();
        ret_im += ret.imag();
    }

    return std::complex<double>(ret_re, ret_im);
}

void AnharmonicCore::calc_phi4_reciprocal(const double *xk1,
                                          const double *xk2,
                                          const double *xk3,
                                          const PhaseFactorStorage *phase_storage_in,
                                          std::complex<double> *ret)
{
    int i, j;
    double phase;
    std::complex<double> ret_in;
    unsigned int nsize_group;

    const auto tune_type_now = phase_storage_in->get_tune_type();

    if (tune_type_now == 1) {

#pragma omp parallel for private(ret_in, nsize_group, j, phase)
        for (i = 0; i < ngroup_v4; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v4[i].size();

            for (j = 0; j < nsize_group; ++j) {
                phase = relvec_v4[i][j].vecs[0][0] * xk1[0]
                        + relvec_v4[i][j].vecs[0][1] * xk1[1]
                        + relvec_v4[i][j].vecs[0][2] * xk1[2]
                        + relvec_v4[i][j].vecs[1][0] * xk2[0]
                        + relvec_v4[i][j].vecs[1][1] * xk2[1]
                        + relvec_v4[i][j].vecs[1][2] * xk2[2]
                        + relvec_v4[i][j].vecs[2][0] * xk3[0]
                        + relvec_v4[i][j].vecs[2][1] * xk3[1]
                        + relvec_v4[i][j].vecs[2][2] * xk3[2];

                ret_in += fcs_group_v4[i][j] * phase_storage_in->get_exp_type1(phase);
            }
            ret[i] = ret_in;
        }

    } else if (tune_type_now == 2) {

        // Tuned version is used when nk1=nk2=nk3 doesn't hold.

        double phase3[3];

#pragma omp parallel for private(ret_in, nsize_group, j, phase3)
        for (i = 0; i < ngroup_v4; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v4[i].size();

            for (j = 0; j < nsize_group; ++j) {
                for (auto ii = 0; ii < 3; ++ii) {
                    phase3[ii]
                            = relvec_v4[i][j].vecs[0][ii] * xk1[ii]
                              + relvec_v4[i][j].vecs[1][ii] * xk2[ii]
                              + relvec_v4[i][j].vecs[2][ii] * xk3[ii];
                }
                ret_in += fcs_group_v4[i][j] * phase_storage_in->get_exp_type2(phase3);
            }
            ret[i] = ret_in;
        }
    } else {
        // Original version
#pragma omp parallel for private(ret_in, nsize_group, phase)
        for (i = 0; i < ngroup_v4; ++i) {

            ret_in = std::complex<double>(0.0, 0.0);
            nsize_group = fcs_group_v4[i].size();

            for (j = 0; j < nsize_group; ++j) {
                phase = relvec_v4[i][j].vecs[0][0] * xk1[0]
                        + relvec_v4[i][j].vecs[0][1] * xk1[1]
                        + relvec_v4[i][j].vecs[0][2] * xk1[2]
                        + relvec_v4[i][j].vecs[1][0] * xk2[0]
                        + relvec_v4[i][j].vecs[1][1] * xk2[1]
                        + relvec_v4[i][j].vecs[1][2] * xk2[2]
                        + relvec_v4[i][j].vecs[2][0] * xk3[0]
                        + relvec_v4[i][j].vecs[2][1] * xk3[1]
                        + relvec_v4[i][j].vecs[2][2] * xk3[2];

                ret_in += fcs_group_v4[i][j] * std::exp(im * phase);
            }
            ret[i] = ret_in;
        }
    }
}

std::complex<double> AnharmonicCore::V3_mode(int mode,
                                             const double *xk2,
                                             const double *xk3,
                                             int is,
                                             int js,
                                             double **eval,
                                             std::complex<double> ***evec) const
{
    std::complex<double> ctmp = std::complex<double>(0.0, 0.0);

    // Return zero if any of the involving phonon has imaginary frequency
    if (eval[0][mode] < eps8 || eval[1][is] < eps8 || eval[2][js] < eps8) return 0.0;

    unsigned int ielem = 0;

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

        auto vec_tmp = evec[0][mode][evec_index_v3[i][0]]
                       * evec[1][is][evec_index_v3[i][1]]
                       * evec[2][js][evec_index_v3[i][2]]
                       * invmass_v3[i];

        auto ret_in = std::complex<double>(0.0, 0.0);

        const int nsize_group = fcs_group_v3[i].size();

        for (auto j = 0; j < nsize_group; ++j) {

            auto phase = relvec_v3[i][j].vecs[0][0] * xk2[0]
                         + relvec_v3[i][j].vecs[0][1] * xk2[1]
                         + relvec_v3[i][j].vecs[0][2] * xk2[2]
                         + relvec_v3[i][j].vecs[1][0] * xk3[0]
                         + relvec_v3[i][j].vecs[1][1] * xk3[1]
                         + relvec_v3[i][j].vecs[1][2] * xk3[2];

            ret_in += fcs_group_v3[i][j] * std::exp(im * phase);

            ++ielem;
        }
        ctmp += ret_in * vec_tmp;
    }

    return ctmp / std::sqrt(eval[0][mode] * eval[1][is] * eval[2][js]);
}

void AnharmonicCore::calc_damping_smearing(const unsigned int ntemp,
                                           const double *temp_in,
                                           const double omega_in,
                                           const unsigned int ik_in,
                                           const unsigned int is_in,
                                           const KpointMeshUniform *kmesh_in,
                                           const double *const *eval_in,
                                           const std::complex<double> *const *const *evec_in,
                                           double *ret)
{
    // This function returns the imaginary part of phonon self-energy
    // for the given frequency omega_in.
    // Lorentzian or Gaussian smearing will be used.
    // This version employs the crystal symmetry to reduce the computational cost

    const auto nk = kmesh_in->nk;
    const auto ns = dynamical->neval;
    const auto ns2 = ns * ns;
    unsigned int i;
    int ik;
    unsigned int is, js;
    unsigned int arr[3];

    int k1, k2;

    double T_tmp;
    double n1, n2;
    double omega_inner[2];

    double multi;

    for (i = 0; i < ntemp; ++i) ret[i] = 0.0;

    double **v3_arr;
    double ***delta_arr;
    double ret_tmp;

    double f1, f2;

    const auto epsilon = integration->epsilon;

    std::vector<KsListGroup> triplet;

    kmesh_in->get_unique_triplet_k(ik_in,
                                   symmetry->SymmList,
                                   false,
                                   false,
                                   triplet);

    const auto npair_uniq = triplet.size();

    allocate(v3_arr, npair_uniq, ns * ns);
    allocate(delta_arr, npair_uniq, ns * ns, 2);

    const auto knum = kmesh_in->kpoint_irred_all[ik_in][0].knum;
    const auto knum_minus = kmesh_in->kindex_minus_xk[knum];
#ifdef _OPENMP
#pragma omp parallel for private(multi, arr, k1, k2, is, js, omega_inner)
#endif
    for (ik = 0; ik < npair_uniq; ++ik) {
        multi = static_cast<double>(triplet[ik].group.size());

        arr[0] = ns * knum_minus + is_in;

        k1 = triplet[ik].group[0].ks[0];
        k2 = triplet[ik].group[0].ks[1];

        for (is = 0; is < ns; ++is) {
            arr[1] = ns * k1 + is;
            omega_inner[0] = eval_in[k1][is];

            for (js = 0; js < ns; ++js) {
                arr[2] = ns * k2 + js;
                omega_inner[1] = eval_in[k2][js];

                if (integration->ismear == 0) {
                    delta_arr[ik][ns * is + js][0]
                            = delta_lorentz(omega_in - omega_inner[0] - omega_inner[1], epsilon)
                              - delta_lorentz(omega_in + omega_inner[0] + omega_inner[1], epsilon);
                    delta_arr[ik][ns * is + js][1]
                            = delta_lorentz(omega_in - omega_inner[0] + omega_inner[1], epsilon)
                              - delta_lorentz(omega_in + omega_inner[0] - omega_inner[1], epsilon);
                } else if (integration->ismear == 1) {
                    delta_arr[ik][ns * is + js][0]
                            = delta_gauss(omega_in - omega_inner[0] - omega_inner[1], epsilon)
                              - delta_gauss(omega_in + omega_inner[0] + omega_inner[1], epsilon);
                    delta_arr[ik][ns * is + js][1]
                            = delta_gauss(omega_in - omega_inner[0] + omega_inner[1], epsilon)
                              - delta_gauss(omega_in + omega_inner[0] - omega_inner[1], epsilon);
                }
            }
        }
    }

    for (ik = 0; ik < npair_uniq; ++ik) {

        k1 = triplet[ik].group[0].ks[0];
        k2 = triplet[ik].group[0].ks[1];

        multi = static_cast<double>(triplet[ik].group.size());

        for (int ib = 0; ib < ns2; ++ib) {
            is = ib / ns;
            js = ib % ns;

            arr[0] = ns * knum_minus + is_in;
            arr[1] = ns * k1 + is;
            arr[2] = ns * k2 + js;

            v3_arr[ik][ib] = std::norm(V3(arr,
                                          kmesh_in->xk,
                                          eval_in,
                                          evec_in,
                                          phase_storage_dos)) * multi;
        }
    }

    for (i = 0; i < ntemp; ++i) {
        T_tmp = temp_in[i];
        ret_tmp = 0.0;
#ifdef _OPENMP
#pragma omp parallel for private(k1, k2, is, js, omega_inner, n1, n2, f1, f2), reduction(+:ret_tmp)
#endif
        for (ik = 0; ik < npair_uniq; ++ik) {

            k1 = triplet[ik].group[0].ks[0];
            k2 = triplet[ik].group[0].ks[1];

            for (is = 0; is < ns; ++is) {

                omega_inner[0] = eval_in[k1][is];

                for (js = 0; js < ns; ++js) {

                    omega_inner[1] = eval_in[k2][js];

                    if (thermodynamics->classical) {
                        f1 = thermodynamics->fC(omega_inner[0], T_tmp);
                        f2 = thermodynamics->fC(omega_inner[1], T_tmp);

                        n1 = f1 + f2;
                        n2 = f1 - f2;
                    } else {
                        f1 = thermodynamics->fB(omega_inner[0], T_tmp);
                        f2 = thermodynamics->fB(omega_inner[1], T_tmp);

                        n1 = f1 + f2 + 1.0;
                        n2 = f1 - f2;
                    }

                    ret_tmp += v3_arr[ik][ns * is + js]
                               * (n1 * delta_arr[ik][ns * is + js][0]
                                  - n2 * delta_arr[ik][ns * is + js][1]);
                }
            }
        }
        ret[i] = ret_tmp;
    }

    deallocate(v3_arr);
    deallocate(delta_arr);
    triplet.clear();

    for (i = 0; i < ntemp; ++i) ret[i] *= pi * std::pow(0.5, 4) / static_cast<double>(nk);
}

void AnharmonicCore::calc_damping_tetrahedron(const unsigned int ntemp,
                                              const double *temp_in,
                                              const double omega_in,
                                              const unsigned int ik_in,
                                              const unsigned int is_in,
                                              const KpointMeshUniform *kmesh_in,
                                              const double *const *eval_in,
                                              const std::complex<double> *const *const *evec_in,
                                              double *ret)
{
    // This function returns the imaginary part of phonon self-energy
    // for the given frequency omega_in.
    // Tetrahedron method will be used.
    // This version employs the crystal symmetry to reduce the computational cost

    const int nk = kmesh_in->nk;
    const int ns = dynamical->neval;

    int ik, ib;
    const auto ns2 = ns * ns;

    unsigned int i;
    unsigned int jk;
    unsigned int is, js;
    unsigned int k1, k2;
    unsigned int arr[3];

    double T_tmp;
    double n1, n2;
    double f1, f2;
    double multi;

    double xk_tmp[3];
    double omega_inner[2];

    double ret_tmp;

    unsigned int *kmap_identity;
    double **energy_tmp;
    double **weight_tetra;
    double **v3_arr;
    double ***delta_arr;

    std::vector<KsListGroup> triplet;

    for (i = 0; i < ntemp; ++i) ret[i] = 0.0;

    kmesh_in->get_unique_triplet_k(ik_in,
                                   symmetry->SymmList,
                                   use_triplet_symmetry,
                                   sym_permutation,
                                   triplet);

    const auto npair_uniq = triplet.size();

    allocate(v3_arr, npair_uniq, ns2);
    allocate(delta_arr, npair_uniq, ns2, 2);

    const auto knum = kmesh_in->kpoint_irred_all[ik_in][0].knum;
    const auto knum_minus = kmesh_in->kindex_minus_xk[knum];
    const auto xk = kmesh_in->xk;

    allocate(kmap_identity, nk);

    for (i = 0; i < nk; ++i) kmap_identity[i] = i;

#ifdef _OPENMP
#pragma omp parallel private(is, js, k1, k2, xk_tmp, energy_tmp, i, weight_tetra, ik, jk, arr)
#endif
    {
        allocate(energy_tmp, 3, nk);
        allocate(weight_tetra, 3, nk);

#ifdef _OPENMP
#pragma omp for
#endif
        for (ib = 0; ib < ns2; ++ib) {
            is = ib / ns;
            js = ib % ns;

            for (k1 = 0; k1 < nk; ++k1) {

                // Prepare two-phonon frequency for the tetrahedron method

                for (i = 0; i < 3; ++i) xk_tmp[i] = xk[knum][i] - xk[k1][i];

                k2 = kmesh_in->get_knum(xk_tmp);

                energy_tmp[0][k1] = eval_in[k1][is] + eval_in[k2][js];
                energy_tmp[1][k1] = eval_in[k1][is] - eval_in[k2][js];
                energy_tmp[2][k1] = -energy_tmp[1][k1];
            }

            for (i = 0; i < 3; ++i) {
                integration->calc_weight_tetrahedron(nk, kmap_identity,
                                                     energy_tmp[i], omega_in,
                                                     dos->tetra_nodes_dos->get_ntetra(),
                                                     dos->tetra_nodes_dos->get_tetras(),
                                                     weight_tetra[i]);
            }

            for (ik = 0; ik < npair_uniq; ++ik) {
                jk = triplet[ik].group[0].ks[0];
                delta_arr[ik][ib][0] = weight_tetra[0][jk];
                delta_arr[ik][ib][1] = weight_tetra[1][jk] - weight_tetra[2][jk];
            }
        }

        deallocate(energy_tmp);
        deallocate(weight_tetra);
    }

    for (ik = 0; ik < npair_uniq; ++ik) {

        k1 = triplet[ik].group[0].ks[0];
        k2 = triplet[ik].group[0].ks[1];

        multi = static_cast<double>(triplet[ik].group.size());

        for (ib = 0; ib < ns2; ++ib) {
            is = ib / ns;
            js = ib % ns;

            if (delta_arr[ik][ib][0] > 0.0 || std::abs(delta_arr[ik][ib][1]) > 0.0) {

                arr[0] = ns * knum_minus + is_in;
                arr[1] = ns * k1 + is;
                arr[2] = ns * k2 + js;

                v3_arr[ik][ib] = std::norm(V3(arr,
                                              kmesh_in->xk,
                                              eval_in,
                                              evec_in,
                                              phase_storage_dos)) * multi;

            } else {
                v3_arr[ik][ib] = 0.0;
            }
        }
    }

    for (i = 0; i < ntemp; ++i) {
        T_tmp = temp_in[i];
        ret_tmp = 0.0;
#ifdef _OPENMP
#pragma omp parallel for private(k1, k2, is, js, omega_inner, n1, n2, f1, f2), reduction(+:ret_tmp)
#endif
        for (ik = 0; ik < npair_uniq; ++ik) {

            k1 = triplet[ik].group[0].ks[0];
            k2 = triplet[ik].group[0].ks[1];

            for (is = 0; is < ns; ++is) {

                omega_inner[0] = eval_in[k1][is];

                for (js = 0; js < ns; ++js) {

                    omega_inner[1] = eval_in[k2][js];

                    if (thermodynamics->classical) {
                        f1 = thermodynamics->fC(omega_inner[0], T_tmp);
                        f2 = thermodynamics->fC(omega_inner[1], T_tmp);

                        n1 = f1 + f2;
                        n2 = f1 - f2;
                    } else {
                        f1 = thermodynamics->fB(omega_inner[0], T_tmp);
                        f2 = thermodynamics->fB(omega_inner[1], T_tmp);

                        n1 = f1 + f2 + 1.0;
                        n2 = f1 - f2;
                    }

                    ret_tmp += v3_arr[ik][ns * is + js]
                               * (n1 * delta_arr[ik][ns * is + js][0]
                                  - n2 * delta_arr[ik][ns * is + js][1]);
                }
            }
        }
        ret[i] = ret_tmp;
    }

    deallocate(v3_arr);
    deallocate(delta_arr);
    deallocate(kmap_identity);

    for (i = 0; i < ntemp; ++i) ret[i] *= pi * std::pow(0.5, 4);
}

void AnharmonicCore::setup_cubic()
{
    int i;
    double *invsqrt_mass_p;

    // Sort force_constant[1] using the operator defined in fcs_phonons.h
    // This sorting is necessary.
    std::sort(fcs_phonon->force_constant_with_cell[1].begin(),
              fcs_phonon->force_constant_with_cell[1].end());
    prepare_group_of_force_constants(fcs_phonon->force_constant_with_cell[1],
                                     3, ngroup_v3, fcs_group_v3);

    allocate(invmass_v3, ngroup_v3);
    allocate(evec_index_v3, ngroup_v3, 3);
    allocate(relvec_v3, ngroup_v3);
    allocate(phi3_reciprocal, ngroup_v3);

    prepare_relative_vector(fcs_phonon->force_constant_with_cell[1],
                            3,
                            ngroup_v3,
                            fcs_group_v3,
                            relvec_v3);

    allocate(invsqrt_mass_p, system->natmin);

    for (i = 0; i < system->natmin; ++i) {
        invsqrt_mass_p[i] = std::sqrt(1.0 / system->mass[system->map_p2s[i][0]]);
    }

    int k = 0;
    for (i = 0; i < ngroup_v3; ++i) {
        for (int j = 0; j < 3; ++j) {
            evec_index_v3[i][j] = fcs_phonon->force_constant_with_cell[1][k].pairs[j].index;
        }
        invmass_v3[i]
                = invsqrt_mass_p[evec_index_v3[i][0] / 3]
                  * invsqrt_mass_p[evec_index_v3[i][1] / 3]
                  * invsqrt_mass_p[evec_index_v3[i][2] / 3];
        k += fcs_group_v3[i].size();
    }

    deallocate(invsqrt_mass_p);
}

void AnharmonicCore::setup_quartic()
{
    int i;
    double *invsqrt_mass_p;
    std::sort(fcs_phonon->force_constant_with_cell[2].begin(),
              fcs_phonon->force_constant_with_cell[2].end());
    prepare_group_of_force_constants(fcs_phonon->force_constant_with_cell[2],
                                     4, ngroup_v4, fcs_group_v4);

    allocate(invmass_v4, ngroup_v4);
    allocate(evec_index_v4, ngroup_v4, 4);
    allocate(relvec_v4, ngroup_v4);
    allocate(phi4_reciprocal, ngroup_v4);

    prepare_relative_vector(fcs_phonon->force_constant_with_cell[2],
                            4,
                            ngroup_v4,
                            fcs_group_v4,
                            relvec_v4);

    allocate(invsqrt_mass_p, system->natmin);

    for (i = 0; i < system->natmin; ++i) {
        invsqrt_mass_p[i] = std::sqrt(1.0 / system->mass[system->map_p2s[i][0]]);
    }

    int k = 0;
    for (i = 0; i < ngroup_v4; ++i) {
        for (int j = 0; j < 4; ++j) {
            evec_index_v4[i][j] = fcs_phonon->force_constant_with_cell[2][k].pairs[j].index;
        }
        invmass_v4[i]
                = invsqrt_mass_p[evec_index_v4[i][0] / 3]
                  * invsqrt_mass_p[evec_index_v4[i][1] / 3]
                  * invsqrt_mass_p[evec_index_v4[i][2] / 3]
                  * invsqrt_mass_p[evec_index_v4[i][3] / 3];
        k += fcs_group_v4[i].size();
    }

    deallocate(invsqrt_mass_p);
}

void PhaseFactorStorage::create(const bool use_tuned_ver,
                                const bool switch_to_type2)
{
    // For accelerating function V3 and V4 by avoiding continual call of std::exp.

    if (use_tuned_ver) {

        const auto inv2pi = 1.0 / (2.0 * pi);

        for (auto i = 0; i < 3; ++i) dnk[i] = static_cast<double>(nk_grid[i]) * inv2pi;

        tune_type = 1;

        if (nk_grid[0] == nk_grid[1] && nk_grid[1] == nk_grid[2]) {
            nk_represent = nk_grid[0];
        } else if (nk_grid[0] == nk_grid[1] && nk_grid[2] == 1) {
            nk_represent = nk_grid[0];
        } else if (nk_grid[1] == nk_grid[2] && nk_grid[0] == 1) {
            nk_represent = nk_grid[1];
        } else if (nk_grid[2] == nk_grid[0] && nk_grid[1] == 1) {
            nk_represent = nk_grid[2];
        } else if (nk_grid[0] == 1 && nk_grid[1] == 1) {
            nk_represent = nk_grid[2];
        } else if (nk_grid[1] == 1 && nk_grid[2] == 1) {
            nk_represent = nk_grid[0];
        } else if (nk_grid[2] == 1 && nk_grid[0] == 1) {
            nk_represent = nk_grid[1];
        } else {
            tune_type = 2;
        }

        // Force using tune_type == 2 version
        if (switch_to_type2) tune_type = 2;

        int ii, jj, kk;

        if (tune_type == 1) {

            double phase;
            dnk_represent = static_cast<double>(nk_represent) * inv2pi;
            const auto inv_dnk_represent = 1.0 / dnk_represent;

            // Pre-calculate the phase factor exp[i 2pi * phase]
            // for different phase angles ranging from [-2pi + 2pi/nk_represent: 2pi*(nk_represent-1)/nk_represent].
            // The redundancy of the data here is intentional and helpful for accepting
            // both positive and negative modulo.
            allocate(exp_phase, 2 * nk_represent - 1);
#ifdef _OPENMP
#pragma omp parallel for private(phase)
#endif
            for (ii = 0; ii < 2 * nk_represent - 1; ++ii) {
                phase = static_cast<double>(ii - nk_represent + 1) * inv_dnk_represent;
                exp_phase[ii] = std::exp(im * phase);
            }

        } else if (tune_type == 2) {

            double phase[3];
            double inv_dnk[3];

            for (auto i = 0; i < 3; ++i) inv_dnk[i] = 1.0 / dnk[i];

            allocate(exp_phase3,
                     2 * nk_grid[0] - 1,
                     2 * nk_grid[1] - 1,
                     2 * nk_grid[2] - 1);
#ifdef _OPENMP
#pragma omp parallel for private(phase, jj, kk)
#endif
            for (ii = 0; ii < 2 * nk_grid[0] - 1; ++ii) {
                phase[0] = static_cast<double>(ii - nk_grid[0] + 1) * inv_dnk[0];
                for (jj = 0; jj < 2 * nk_grid[1] - 1; ++jj) {
                    phase[1] = static_cast<double>(jj - nk_grid[1] + 1) * inv_dnk[1];
                    for (kk = 0; kk < 2 * nk_grid[2] - 1; ++kk) {
                        phase[2] = static_cast<double>(kk - nk_grid[2] + 1) * inv_dnk[2];
                        exp_phase3[ii][jj][kk] = std::exp(im * (phase[0] + phase[1] + phase[2]));
                    }
                }
            }
        }
    } else {
        tune_type = 0;
    }
}

unsigned int PhaseFactorStorage::get_tune_type() const
{
    return tune_type;
}

std::complex<double> PhaseFactorStorage::get_exp_type1(const double phase_in) const
{
    int iloc = nint(phase_in * dnk_represent) % nk_represent + nk_represent - 1;
    return exp_phase[iloc];
}

std::complex<double> PhaseFactorStorage::get_exp_type2(const double *phase3_in) const
{
    int loc[3];
    for (auto i = 0; i < 3; ++i) {
        loc[i] = nint(phase3_in[i] * dnk[i]) % nk_grid[i] + nk_grid[i] - 1;
    }
    return exp_phase3[loc[0]][loc[1]][loc[2]];
}

void AnharmonicCore::calc_self3omega_tetrahedron(const double Temp,
                                                 const KpointMeshUniform *kmesh_in,
                                                 const double *const *eval_in,
                                                 const std::complex<double> *const *const *evec_in,
                                                 const unsigned int ik_in,
                                                 const unsigned int snum,
                                                 const unsigned int nomega,
                                                 const double *omega,
                                                 double *ret)
{
    // This function returns the imaginary part of phonon self-energy
    // for the given frequency range of omega, phonon frequency (eval) and phonon eigenvectors (evec).
    // The tetrahedron method will be used.
    // This version employs the crystal symmetry to reduce the computational cost
    // In addition, both MPI and OpenMP parallelization are used in a hybrid way inside this function.

    const auto nk = kmesh_in->nk;
    const int ns = dynamical->neval;

    int ik, ib, iomega;
    const auto ns2 = ns * ns;

    unsigned int i;
    unsigned int is, js;
    unsigned int k1, k2;
    unsigned int arr[3];
    unsigned int nk_tmp;

    double n1, n2;
    double f1, f2;
    double omega_inner[2];

    unsigned int *kmap_identity;
    int **kpairs;
    double **energy_tmp;
    double **weight_tetra;
    double **v3_arr, *v3_arr_loc;
    double *ret_private;

    std::vector<KsListGroup> triplet;
    std::vector<int> vk_l;

    const int knum = kmesh_in->kpoint_irred_all[ik_in][0].knum;
    const int knum_minus = kmesh_in->kindex_minus_xk[knum];

    kmesh_in->get_unique_triplet_k(ik_in,
                                   symmetry->SymmList,
                                   false,
                                   false,
                                   triplet);

    const auto npair_uniq = triplet.size();

    if (npair_uniq != nk) {
        exit("calc_self3omega_tetrahedron", "Something is wrong.");
    }

    allocate(kpairs, nk, 2);
    allocate(kmap_identity, nk);

    for (i = 0; i < nk; ++i) kmap_identity[i] = i;

    for (iomega = 0; iomega < nomega; ++iomega) ret[iomega] = 0.0;

    for (ik = 0; ik < npair_uniq; ++ik) {
        kpairs[ik][0] = triplet[ik].group[0].ks[0];
        kpairs[ik][1] = triplet[ik].group[0].ks[1];
    }

    if (nk % mympi->nprocs != 0) {
        nk_tmp = nk / mympi->nprocs + 1;
    } else {
        nk_tmp = nk / mympi->nprocs;
    }

    vk_l.clear();

    for (ik = 0; ik < nk; ++ik) {
        if (ik % mympi->nprocs == mympi->my_rank) {
            vk_l.push_back(ik);
        }
    }

    if (vk_l.size() < nk_tmp) {
        vk_l.push_back(-1);
    }

    allocate(v3_arr_loc, ns2);
    allocate(v3_arr, nk_tmp * mympi->nprocs, ns2);

    for (ik = 0; ik < nk_tmp; ++ik) {

        int ik_now = vk_l[ik];

        if (ik_now == -1) {

            for (ib = 0; ib < ns2; ++ib) v3_arr_loc[ib] = 0.0; // do nothing

        } else {
#ifdef _OPENMP
#pragma omp parallel for private(is, js, arr)
#endif
            for (ib = 0; ib < ns2; ++ib) {

                is = ib / ns;
                js = ib % ns;

                arr[0] = ns * knum_minus + snum;
                arr[1] = ns * kpairs[ik_now][0] + is;
                arr[2] = ns * kpairs[ik_now][1] + js;

                v3_arr_loc[ib] = std::norm(V3(arr,
                                              kmesh_in->xk,
                                              eval_in,
                                              evec_in,
                                              phase_storage_dos));
            }
        }
        MPI_Gather(&v3_arr_loc[0], ns2, MPI_DOUBLE,
                   v3_arr[ik * mympi->nprocs], ns2,
                   MPI_DOUBLE, 0, MPI_COMM_WORLD);
    }
    deallocate(v3_arr_loc);

    if (mympi->my_rank == 0) {

#ifdef _OPENMP
#pragma omp parallel private(is, js, k1, k2, energy_tmp, i, \
                             iomega, weight_tetra, ik, \
                             omega_inner, f1, f2, n1, n2)
#endif
        {
            allocate(energy_tmp, 2, nk);
            allocate(weight_tetra, 2, nk);
#ifdef _OPENMP
            const int nthreads = omp_get_num_threads();
            const int ithread = omp_get_thread_num();
#else
            const int nthreads = 1;
            const int ithread = 0;
#endif

#ifdef _OPENMP
#pragma omp single
#endif
            {
                allocate(ret_private, nthreads * nomega);
                for (i = 0; i < nthreads * nomega; ++i) ret_private[i] = 0.0;
            }
#ifdef _OPENMP
#pragma omp for
#endif
            for (ib = 0; ib < ns2; ++ib) {

                is = ib / ns;
                js = ib % ns;

                for (ik = 0; ik < nk; ++ik) {
                    k1 = kpairs[ik][0];
                    k2 = kpairs[ik][1];

                    energy_tmp[0][ik] = eval_in[k1][is] + eval_in[k2][js];
                    energy_tmp[1][ik] = eval_in[k1][is] - eval_in[k2][js];
                }
                for (iomega = 0; iomega < nomega; ++iomega) {
                    for (i = 0; i < 2; ++i) {
                        integration->calc_weight_tetrahedron(nk, kmap_identity,
                                                             energy_tmp[i], omega[iomega],
                                                             dos->tetra_nodes_dos->get_ntetra(),
                                                             dos->tetra_nodes_dos->get_tetras(),
                                                             weight_tetra[i]);
                    }

                    for (ik = 0; ik < nk; ++ik) {
                        k1 = kpairs[ik][0];
                        k2 = kpairs[ik][1];

                        omega_inner[0] = eval_in[k1][is];
                        omega_inner[1] = eval_in[k2][js];
                        if (thermodynamics->classical) {
                            f1 = thermodynamics->fC(omega_inner[0], Temp);
                            f2 = thermodynamics->fC(omega_inner[1], Temp);
                            n1 = f1 + f2;
                            n2 = f1 - f2;
                        } else {
                            f1 = thermodynamics->fB(omega_inner[0], Temp);
                            f2 = thermodynamics->fB(omega_inner[1], Temp);
                            n1 = f1 + f2 + 1.0;
                            n2 = f1 - f2;
                        }

                        //#pragma omp critical
                        ret_private[nomega * ithread + iomega]
                                += v3_arr[ik][ib] * (n1 * weight_tetra[0][ik] - 2.0 * n2 * weight_tetra[1][ik]);
                    }
                }
            }
#ifdef _OPENMP
#pragma omp for
#endif
            for (iomega = 0; iomega < nomega; ++iomega) {
                for (int t = 0; t < nthreads; t++) {
                    ret[iomega] += ret_private[nomega * t + iomega];
                }
            }
            deallocate(energy_tmp);
            deallocate(weight_tetra);
        }
#ifdef _OPENMP
#pragma omp parallel for
#endif
        for (iomega = 0; iomega < nomega; ++iomega) {
            ret[iomega] *= pi * std::pow(0.5, 4);
        }
        deallocate(ret_private);
    }

    deallocate(v3_arr);
    deallocate(kmap_identity);
    deallocate(kpairs);
}

int AnharmonicCore::get_ngroup_fcs(const unsigned int order) const
{
    if (order == 3) return ngroup_v3;
    if (order == 4) return ngroup_v4;
    return 0;
}

std::vector<double> *AnharmonicCore::get_fcs_group(const unsigned int order) const
{
    if (order == 3) return fcs_group_v3;
    if (order == 4) return fcs_group_v4;
    return nullptr;
}

double *AnharmonicCore::get_invmass_factor(const unsigned int order) const
{
    if (order == 3) return invmass_v3;
    if (order == 4) return invmass_v4;
    return nullptr;
}

int **AnharmonicCore::get_evec_index(const unsigned int order) const
{
    if (order == 3) return evec_index_v3;
    if (order == 4) return evec_index_v4;
    return nullptr;
}


/*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * Written (W) 2013 Nicolo' Navarin
  */

#include "DDK_FeatureGenerator.h"
 //#include <shogun/features/Labels.h>
#include <climits>
#include <deque>
//#include <boost/container/stable_vector.hpp>
//#include <boost/random/uniform_int.hpp>
//#include <boost/random/random_number_generator.hpp>
//#include <boost/random.hpp>
//#include <boost/generator_iterator.hpp>

//#include <boost/random/variate_generator.hpp>
//#include <boost/generator_iterator.hpp>

// #include <shogun/mathematics/Math.h>

 //TODO eliminare
 double lossy_e=0.001;

 DDkernel_FeatureGenerator::DDkernel_FeatureGenerator()
 : learn_rate(0.1), max_iter(1000), NSPDK_FeatureGenerator("DDK")
 {
 }
/*
 DDkernel_FeatureGenerator::DDkernel_FeatureGenerator(CGraphFeatures* traindat, CLabels* trainlab)
 :  learn_rate(.1), max_iter(1000), NSPDK_FeatureGenerator("DDK")
 {
     //TODO inizializzazione
   //  set_features(traindat);
   //  set_labels(trainlab);
 }*/

 DDkernel_FeatureGenerator::~DDkernel_FeatureGenerator()
 {
	 NSPDK_FeatureGenerator("ddk");
 }
 DDkernel_FeatureGenerator::DDkernel_FeatureGenerator(const std::string& id) :
 		NSPDK_FeatureGenerator(id) {
		new_flag(&mTreeLambda, "mTreeLambda", "(double)\n tree_lambda parameter for DDK kernel");
 }
 void DDkernel_FeatureGenerator::OutputParameters(ostream& out) const {
 	out << "Radius: " << mRadius << endl;
 	out << "Radius2: " << mRadiusTwo << endl;

 	out << "Distance: " << mDistance << endl;
 	out << "Match_Type: " << mMatchType << endl;
 	out << "Hash_Bit_Size: " << mHashBitSize << endl;
 	out << "Hash_Bit_mask: " << mHashBitMask << endl;
 	out << "Min_Kernel: " << mMinKernel << endl;
 	out << "Normalization: " << mNormalization << endl;
 	out << "Vertex Degree Threshold: " << mVertexDegreeThreshold << endl;
 	out << "Debug_Verbosity: " << mDebugVerbosity << endl;
 	out << "Tree_lambda: " << mTreeLambda << endl;

 }

 void DDkernel_FeatureGenerator::generate_feature_vector(const GraphClass& aG, SVector& x, const vector<unsigned>& aFirstEndpointList) {
 }
	 /*
	 // OutputParameters(cout);
	 vector<GraphClass> graphs;
	 graphs.push_back(aG);
	 CGraphFeatures* data = new CGraphFeatures(graphs);
	// set_lambda(mTreeLambda);
	 //TODO dag_h is now set as the radius parameter r
	// cout<<"dag_h="<<mRadius<<endl;
	 svectorFeatures(data,x,mRadius+1 ,10000000);
	// data->;
	 graphs.clear();
	 delete data;

 }*/

 void DDkernel_FeatureGenerator::generate_vertex_feature_vector(const GraphClass& aG, vector<SVector>& x_list, const vector<unsigned>& aFirstEndpointList) {

 }

 void DDkernel_FeatureGenerator::GenerateAbstractVertexFeatures(unsigned aSrcID, const GraphClass& aG, unsigned aRadius, unsigned aDistance, SVector& x) {

 }

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

//TODO output on svector
 /*
 void DDkernel_FeatureGenerator::svectorFeatures(CGraphFeatures* data,SVector& out_vector, int dag_h, int max_features = 10000000){
      //  vector<mymapForHash*> HashMaps = vector<mymapForHash*>(lhs->get_num_vectors());
     // for di tutti i grafi cosi creo i bigDAG.

 long time = 1.0 / lossy_e;

 vector<double> BER_total;

 OnlineFeatures onlinefeatures(max_features);
      //  OnlineFeatures onlinefeatures_old;

         int64_t numero = 0;

         int training =   data->get_num_vectors();
         string a;
         ofstream output(a.c_str());

         for (int idx_a=0;idx_a<training;idx_a++){

         Graph* a = new Graph() ;
       int32_t size_a = 0;
       ((CGraphFeatures*) data)->get_feature_vector(a,idx_a);

    //   	 try{
       OnlineHashBig2DAGLossy* test = onlinefeatures.generateFeatures(a, dag_h,idx_a,data->get_num_vectors());

       OnlineHashBig2DAGOrdered ordered_map;
    //TODO add features to a OnlineHashBig2DAGOrdered map
       // cout<<"test: "<<test->size();
        BOOST_FOREACH(OnlineHashBig2DAGLossy::value_type local_row, *test) {
           ordered_map[local_row.second.get<2>()]=local_row.second;
       }

    //   cout<<"ordered_map: "<<ordered_map.size();
        //normalize the feature vector

  //      }
        SVector z;

    unsigned int last = 0;
         BOOST_FOREACH(OnlineHashBig2DAGOrdered::value_type i_row,  ordered_map){
           //  cout<<"lambda "<<lambda<<endl;
				z.set((unsigned int)i_row.second.get<2>() % (unsigned int)pow(2,mHashBitSize), pow((double)mTreeLambda, (double)i_row.second.get<0>() / 2.0)* (double)i_row.second.get<3>());

                 ASSERT((unsigned int)i_row.second.get<2>() >= last);
                 last = (unsigned int)i_row.second.get<2>();
         }
      //   if (mNormalization){
        // 				z.normalize();}

		out_vector.add(z);
		//if (mNormalization){
		//		out_vector.normalize();}

     delete a;
            test->clear();
            ordered_map.clear();
         delete test;

     //  	 }catch(shogun::ShogunException e){
     //  		 cout<<e.get_exception_string();

      // 	 }

       }
     //   cout<<endl;
        output.close();

     //  cout<<"FEATURES has been printed"<<endl;

     }

bool GraphSortCriterion (const tuple<double,Graph,long> p1, const tuple<double,Graph,long> p2)
{
    /// a graph is less than another graph

    return p1.get<0>()<p2.get<0>();
}

bool FeaturesSortCriterion (const tuple<double,OnlineHashBig2DAG,long> p1, const tuple<double,OnlineHashBig2DAG,long> p2)
{
    // a graph is less than another graph

    return p1.get<0>()<p2.get<0>();
}

*/

void DDkernel_FeatureGeneratorNew::generate_feature_vector(const GraphClass& aG, SVector& x, const vector<unsigned>& aFirstEndpointList) {
//	 OnlineFeaturesEDeN of;
//	cout<<"Lambda="<<mTreeLambda<<endl;
//	cout<<"Radius="<<mRadius<<endl;

	 x = generate_feature_vector_core(aG, mRadius);
	 if (mNormalization){
	 				x.normalize();
	 }

}

SVector&  DDkernel_FeatureGeneratorNew::generate_feature_vector_core(const GraphClass& aG , int dag_h){
	//cout<<mTreeLambda<<endl;
	//cout<<"start generation of feature vector.."<<endl;
			SVector* x = new SVector();

		//	cout<<"n of nodes "<<aG.VertexSize()<<endl;
		    for (unsigned i=0; i< aG.VertexSize(); i++){
		    	map<pair<unsigned, unsigned>, int> oSrcDestMaptoDistance;
		    	map<unsigned, vector<unsigned> > MapNodetoParents;
		    	map<unsigned, vector<unsigned> > MapNodetoChildren;

		    	vector<unsigned> TopologicalSort;
		    	int maxLevel = 0;
		    	//aG. // codice calcolo features here
		      	aG.SingleVertexBoundedBreadthFirstVisitTree(i,dag_h, oSrcDestMaptoDistance,  MapNodetoParents,MapNodetoChildren, TopologicalSort, maxLevel);
		//    cout<<"MaxLevel: "<<maxLevel<<endl;
		      	// 	cout<<"Breadth first visit ended.."<<endl;
		      	// I have the DAG for the vertex i. Need topological sort.
		      	//map every node to him productions
		    	map<unsigned, vector<unsigned> > MapNodeToProductionsID;
		    	map<unsigned, vector<int> > MapNodetoFrequencies;

		    	map<unsigned, int > MapProductionIDtoSize;

		  //  	cout<<"topological sort size "<<TopologicalSort.size()<<endl;

		      	//maybe I meed the maximum depth (distance)
		      	for (int ii=TopologicalSort.size()-1; ii>=0; ii--){
		      		// cout<<ii<<endl;
		      		//calculate child size
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		      		int max_child_heigth=0;
		        	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {
		    			int child_vertex_id= vertex_adjacency_list[j];
		    			int child_heigth=MapNodeToProductionsID[child_vertex_id].size();
		    			if(child_heigth > max_child_heigth){
		    				max_child_heigth = child_heigth;
		    			}
		        	}

		      		for (int depth=0; depth<= max_child_heigth; ++depth){ //era maxLevel
		      			unsigned hash_subgraph_code = 1;
		      		//depth 0; only label
		      			if(depth==0){
		      		//		cout<<"depth=0"<<endl;
		      				unsigned enc= Radius0RootedGraphCanonicalFormEncoding(TopologicalSort[ii], aG);
		      		MapNodeToProductionsID[TopologicalSort[ii]].push_back(enc);
		      		// scorro i figli e creo etichetta tramite funzione di hash
		      		// depth > 0; the length of the children ID list
		      		//imposta valore feature a 1 (TODO lambda)
		      		//							level(o-mRadius)
			    	int frequency = 0;
			    	if(max_child_heigth==0){
		      		//leaf node
			    	frequency=  maxLevel - oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])];
			    	}
			    	SVector z;
		      		z.set(enc, (double)(frequency+1.0) * sqrt(mTreeLambda));
			    //	cout<<"Feature: "<<aG.GetVertexLabelConcatenated(TopologicalSort[ii])<<" freq:"<<(double)(frequency+1.0) * sqrt(mTreeLambda)<<" code: "<<enc<<endl;
			    	x->add(z);
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);
		      		MapProductionIDtoSize[enc]= 1; //sqrt(mTreeLambda);
		      			}
		      			else{
		      	//			cout<<"depth="<<depth<<endl;

		      			int size = 0;
		      		string encoding;
		      		encoding = aG.GetVertexLabelConcatenated(TopologicalSort[ii]);
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		    		vector<unsigned>  vertex_label_id_list;
		    		int min_freq_children =INT_MAX;
			    	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {

			    		unsigned child_vertex_id = vertex_adjacency_list[j];
			    		int size_map =  MapNodeToProductionsID[child_vertex_id].size();//min (depth-1) e dim
			    		unsigned child_hash = MapNodeToProductionsID[child_vertex_id][min(size_map,depth)-1];// id hash del figlio;
			    		int freq_child = MapNodetoFrequencies[child_vertex_id][min(size_map,depth)-1];
			    		if (freq_child < min_freq_children){
			    			min_freq_children = freq_child;
			    		}
			    		vertex_label_id_list.push_back(child_hash);
			    		size += MapProductionIDtoSize[child_hash];
			    	}

			    	// vertex_label_id_list contiene gli hash delle prod dei figli. Va ordinato e usato x generare l hash attuale
			    	sort(vertex_label_id_list.begin(), vertex_label_id_list.end());

			    	if (vertex_label_id_list.size() > 0){
			    		std::stringstream ss;
			    		ss <<":"<<vertex_label_id_list[0];
			    		encoding += ss.str() ;
			    	}//originale senza .
			    	for (unsigned i = 1; i < vertex_label_id_list.size(); i++){
			    		std::stringstream ss;
			    		ss << vertex_label_id_list[i];
			    		encoding += "." + ss.str() ;
			    	}
			    //DEBUG	cout<<"encoding "<<encoding<<endl;
			    	//calculate frequency

			    	hash_subgraph_code = HashFunc(encoding);
			    	MapNodeToProductionsID[TopologicalSort[ii]].push_back(hash_subgraph_code);
			    	size += 1; // current node
			    	MapProductionIDtoSize[hash_subgraph_code]= size;
			    	int frequency = min_freq_children;
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);

			    	SVector z;
			   // 	cout<<"Feature: "<<encoding<<" freq:"<<(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size))<<" code: "<<hash_subgraph_code<<endl;
			    	z.set(hash_subgraph_code,(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size)));
			    	x->add(z);

			    //	cout<<"weight "<<sqrt(pow(mTreeLambda,size))<<endl;

		      			}

		      		}
		      	}

		    }
		    return *x;

	 }

DDkernel_FeatureGeneratorNew::DDkernel_FeatureGeneratorNew(const std::string& id) :
		DDkernel_FeatureGenerator(id) {
}

NSDDkernel_FeatureGenerator::NSDDkernel_FeatureGenerator(const std::string& id) :
		DDkernel_FeatureGeneratorNew(id) {
	mRadiusTwo = 0;
			new_flag(&mRadiusTwo, "mRadiusTwo", "(double)\n second radius parameter for NSDDK kernel");

}

void NSDDkernel_FeatureGenerator::generate_feature_vector(const GraphClass& aG, SVector& x, const vector<unsigned>& aFirstEndpointList){
//cout<<mRadiusTwo<<endl;
	 x = generate_feature_vector_core(aG, mRadius);
	//x = generate_feature_vector_core_DISTANCE(aG, mRadius); //TEST distanze

	// if (mNormalization){
	 //				x.normalize();
	// }
}

SVector&   NSDDkernel_FeatureGenerator::generate_feature_vector_core(const GraphClass& aG , int dag_h){
   // test rNSPDK 1/2 d=1/2
	GraphClass ag2 = aG;
	SVector* out = new SVector();
//	cout<<"Generate feature vector core"<<endl;
	for (unsigned i=0; i< aG.VertexSize(); i++){
	//	cout<<"i:"<<i<<endl;

		//SVector DD= generate_vertex_feature_vector(i,ag2 ,dag_h);
		vector<SVector> features; //(dag_h+1);
		SVector DD= generate_vertex_feature_vector_DIVIDED(i,ag2 ,mRadius,features);
	//	cout<<"got DD vector"<<endl;
		//cout<<features.size();
	//	out->add(DD);
		//NSPDK
	//	cout<<"start generation of NSPKD feature vector.. mDistance"<<mDistance<<endl;

		//NSPDK_FeatureGenerator::GenerateVertexFeatures(i,aG,NSPDK);
	//	NSPDK_FeatureGenerator::generate_feature_vector(aG,NSPDK);
		vector<unsigned> first_endpoint_list= vector<unsigned>();
		first_endpoint_list.push_back(i);
			//GetFirstEndpoints(aG, first_endpoint_list);

		int horizon=max(mDistance, mRadiusTwo); //   max(mDistance, mRadius);
			aG.ComputePairwiseDistanceInformation(horizon, first_endpoint_list);
			if (aG.Check() == false)
				throw logic_error("ERROR10: the graph data structure is not sound and it has not passed the checkup procedure"); //check graph data structure soundness

			InitFeatureCache(aG, mRadiusTwo ); //mRadius
			for (unsigned r = 0; r <= mRadius ; r++) { //max(1.0,mRadius / 2.0)
				for (unsigned r2 = 0; r2 <= mRadiusTwo ; r2++) {
				for (unsigned d = 0; d <= mDistance; d++) { // era 0
					SVector z;
					for (unsigned i = 0; i < first_endpoint_list.size(); i++) {
						unsigned src_id = first_endpoint_list[i];
						if (aG.GetVertexViewPoint(src_id) && aG.GetVertexKernelPoint(src_id) && aG.GetVertexAlive(src_id)) { //proceed to extract features only if the *src* vertex is a kernel point and is alive
							SVector zv;

							vector<unsigned> endpoint_list(4);
							endpoint_list[0] = r2;
							endpoint_list[1] = d;

						//	unsigned src_code = GenerateVertexNeighbourhoodHashCode(aSrcID, aG, aRadius);
				//	TODO	for(unsigned src_code in DD)

							vector<unsigned> dest_id_list = aG.GetFixedDistanceVertexIDList(i, d);
							for (unsigned dest_j = 0; dest_j < dest_id_list.size(); dest_j++) {
								unsigned dest_id = dest_id_list[dest_j];
								unsigned dest_code = 0;
								if (aG.GetVertexKernelPoint(dest_id) && aG.GetVertexAlive(dest_id)) { //proceed to extract features only if the *dest* vertex is a kernel point and is alive
									dest_code = GenerateVertexNeighbourhoodHashCode(dest_id, aG, r2); //
									// first is the DDK feature, second NSPDK feature
						//  for(const SVector::Pair *p = DD; p->i>=0; p++) {
						 for(const SVector::Pair *p = features[r]; p->i>=0; p++) {

									endpoint_list[2] = p->i;
										//TODO scorrere le feature di DD
										endpoint_list[3] = dest_code;
									unsigned code = HashFunc(endpoint_list, mHashBitMask);

									SVector z;
									z.set(code, p->v);
									zv.add(z);
								}
								}
							}

							z.add(zv);
						}
						}

					if (mNormalization)
						z.normalize();
				out->add(z);
				} //d
			} //r
			} //r2

	//	cout<<"features size DD:"<<DD.sparse_size()<<" out:"<<out->sparse_size()<<endl;

}
	return *out;
}

SVector&   NSDDkernel_FeatureGenerator::generate_feature_vector_core_DISTANCE(const GraphClass& aG , int dag_h){
   // test rNSPDK 1/2 d=1/2
	GraphClass ag2 = aG;
	SVector* out = new SVector();
//	cout<<"Generate feature vector core"<<endl;
	for (unsigned i=0; i< aG.VertexSize(); i++){
	//	cout<<"i:"<<i<<endl;

		//SVector DD= generate_vertex_feature_vector(i,ag2 ,dag_h);
		vector< vector< SVector> > features; //(dag_h+1);
		SVector DD= generate_vertex_feature_vector_DIVIDED_DISTANCE(i,ag2 ,dag_h,features);
		//cout<<features.size();
	//	out->add(DD);
		//NSPDK
	//	cout<<"start generation of NSPKD feature vector.. mDistance"<<mDistance<<endl;

		//NSPDK_FeatureGenerator::GenerateVertexFeatures(i,aG,NSPDK);
	//	NSPDK_FeatureGenerator::generate_feature_vector(aG,NSPDK);
		vector<unsigned> first_endpoint_list= vector<unsigned>();
		first_endpoint_list.push_back(i);
			//GetFirstEndpoints(aG, first_endpoint_list);

		int horizon=max(mDistance, mRadiusTwo); //   max(mDistance, mRadius);
			aG.ComputePairwiseDistanceInformation(horizon, first_endpoint_list);
			if (aG.Check() == false)
				throw logic_error("ERROR10: the graph data structure is not sound and it has not passed the checkup procedure"); //check graph data structure soundness

			InitFeatureCache(aG, mRadiusTwo ); //mRadius
			for (unsigned r = 0; r <= mRadius ; r++) { //max(1.0,mRadius / 2.0)
				for (unsigned r2 = 0; r2 <= mRadiusTwo ; r2++) {
				for (unsigned d = 0; d <= mDistance; d++) { // era 0
					SVector z;
					for (unsigned i = 0; i < first_endpoint_list.size(); i++) {
						unsigned src_id = first_endpoint_list[i];
						if (aG.GetVertexViewPoint(src_id) && aG.GetVertexKernelPoint(src_id) && aG.GetVertexAlive(src_id)) { //proceed to extract features only if the *src* vertex is a kernel point and is alive
							SVector zv;

							vector<unsigned> endpoint_list(5);
							endpoint_list[0] = r2;
							endpoint_list[1] = d;

						//	unsigned src_code = GenerateVertexNeighbourhoodHashCode(aSrcID, aG, aRadius);
				//	TODO	for(unsigned src_code in DD)

							vector<unsigned> dest_id_list = aG.GetFixedDistanceVertexIDList(i, d);
							for (unsigned dest_j = 0; dest_j < dest_id_list.size(); dest_j++) {
								unsigned dest_id = dest_id_list[dest_j];
								unsigned dest_code = 0;
								if (aG.GetVertexKernelPoint(dest_id) && aG.GetVertexAlive(dest_id)) { //proceed to extract features only if the *dest* vertex is a kernel point and is alive
									dest_code = GenerateVertexNeighbourhoodHashCode(dest_id, aG, r2); //
									// first is the DDK feature, second NSPDK feature
						//  for(const SVector::Pair *p = DD; p->i>=0; p++) {
									for(int distance=0; distance<features[r].size(); distance++){ //dag_h+1
										endpoint_list[4]=distance; //from root in DD
						 for(const SVector::Pair *p = features[r][distance]; p->i>=0; p++) {

									endpoint_list[2] = p->i;
										//TODO scorrere le feature di DD
										endpoint_list[3] = dest_code;
										//TEST output
								//		cout<<"R "<<endpoint_list[0]<<" d "<<endpoint_list[1]<<" DD "<<endpoint_list[2]<<" NSPDK "<<endpoint_list[3]<<" dRoot "<<endpoint_list[4]<<endl;
									unsigned code = HashFunc(endpoint_list, mHashBitMask);

									SVector z;
									z.set(code, p->v);
									zv.add(z);
								}
								}
								}
							}

							z.add(zv);
						}
						}

					if (mNormalization)
						z.normalize();
				out->add(z);
				} //d
			} //r
			}//r2

	//	cout<<"features size DD:"<<DD.sparse_size()<<" out:"<<out->sparse_size()<<endl;

}
	return *out;
}

SVector&   NSDDkernel_FeatureGenerator::generate_vertex_feature_vector(unsigned i, const GraphClass& aG , int dag_h){
	//cout<<"start generation of feature vector.."<<endl;
			SVector* x = new SVector();

		//	cout<<"n of nodes "<<aG.VertexSize()<<endl;
		    assert(i< aG.VertexSize());
		    	map<pair<unsigned, unsigned>, int> oSrcDestMaptoDistance;
		    	map<unsigned, vector<unsigned> > MapNodetoParents;
		    	map<unsigned, vector<unsigned> > MapNodetoChildren;

		    	vector<unsigned> TopologicalSort;
		    	int maxLevel = 0;
		    	//aG. // codice calcolo features here
		      	aG.SingleVertexBoundedBreadthFirstVisitTree(i,dag_h, oSrcDestMaptoDistance,  MapNodetoParents,MapNodetoChildren, TopologicalSort, maxLevel);
		//    cout<<"MaxLevel: "<<maxLevel<<endl;
		      	// 	cout<<"Breadth first visit ended.."<<endl;
		      	// I have the DAG for the vertex i. Need topological sort.
		      	//map every node to him productions
		    	map<unsigned, vector<unsigned> > MapNodeToProductionsID;
		    	map<unsigned, vector<int> > MapNodetoFrequencies;

		    	map<unsigned, int > MapProductionIDtoSize;

		  //  	cout<<"topological sort size "<<TopologicalSort.size()<<endl;

		      	//maybe I meed the maximum depth (distance)
		      	for (int ii=TopologicalSort.size()-1; ii>=0; ii--){
		      		// cout<<ii<<endl;
		      		//calculate child size
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		      		int max_child_heigth=0;
		        	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {
		    			int child_vertex_id= vertex_adjacency_list[j];
		    			int child_heigth=MapNodeToProductionsID[child_vertex_id].size();
		    			if(child_heigth > max_child_heigth){
		    				max_child_heigth = child_heigth;
		    			}
		        	}

		      		for (int depth=0; depth<= max_child_heigth; ++depth){ //era maxLevel
		      			unsigned hash_subgraph_code = 1;
		      		//depth 0; only label
		      			if(depth==0){
		      		//		cout<<"depth=0"<<endl;
		      				unsigned enc= Radius0RootedGraphCanonicalFormEncoding(TopologicalSort[ii], aG);
		      		MapNodeToProductionsID[TopologicalSort[ii]].push_back(enc);
		      		// scorro i figli e creo etichetta tramite funzione di hash
		      		// depth > 0; the length of the children ID list
		      		//imposta valore feature a 1 (TODO lambda)
		      		//							level(o-mRadius)
			    	int frequency = 0;
			    	if(max_child_heigth==0){
		      		//leaf node
			    	frequency=  maxLevel - oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])];
			    	}
			    	SVector z;
		      		z.set(enc, (double)(frequency+1.0) * sqrt(mTreeLambda));
			    //	cout<<"Feature: "<<aG.GetVertexLabelConcatenated(TopologicalSort[ii])<<" freq:"<<(double)(frequency+1.0) * sqrt(mTreeLambda)<<" code: "<<enc<<endl;
			    	x->add(z);
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);
		      		MapProductionIDtoSize[enc]= 1; //sqrt(mTreeLambda);
		      			}
		      			else{
		      	//			cout<<"depth="<<depth<<endl;

		      			int size = 0;
		      		string encoding;
		      		encoding = aG.GetVertexLabelConcatenated(TopologicalSort[ii]);
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		    		vector<unsigned>  vertex_label_id_list;
		    		int min_freq_children =INT_MAX;
			    	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {

			    		unsigned child_vertex_id = vertex_adjacency_list[j];
			    		int size_map =  MapNodeToProductionsID[child_vertex_id].size();//min (depth-1) e dim
			    		unsigned child_hash = MapNodeToProductionsID[child_vertex_id][min(size_map,depth)-1];// id hash del figlio;
			    		int freq_child = MapNodetoFrequencies[child_vertex_id][min(size_map,depth)-1];
			    		if (freq_child < min_freq_children){
			    			min_freq_children = freq_child;
			    		}
			    		vertex_label_id_list.push_back(child_hash);
			    		size += MapProductionIDtoSize[child_hash];
			    	}

			    	// vertex_label_id_list contiene gli hash delle prod dei figli. Va ordinato e usato x generare l hash attuale
			    	sort(vertex_label_id_list.begin(), vertex_label_id_list.end());

			    	if (vertex_label_id_list.size() > 0){
			    		std::stringstream ss;
			    		ss <<":"<<vertex_label_id_list[0];
			    		encoding += ss.str() ;
			    	}//originale senza .
			    	for (unsigned i = 1; i < vertex_label_id_list.size(); i++){
			    		std::stringstream ss;
			    		ss << vertex_label_id_list[i];
			    		encoding += "." + ss.str() ;
			    	}
			    //DEBUG	cout<<"encoding "<<encoding<<endl;
			    	//calculate frequency

			    	hash_subgraph_code = HashFunc(encoding);
			    	MapNodeToProductionsID[TopologicalSort[ii]].push_back(hash_subgraph_code);
			    	size += 1; // current node
			    	MapProductionIDtoSize[hash_subgraph_code]= size;
			    	int frequency = min_freq_children;
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);

			    	SVector z;
			   // 	cout<<"Feature: "<<encoding<<" freq:"<<(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size))<<" code: "<<hash_subgraph_code<<endl;
			    	z.set(hash_subgraph_code,(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size)));
			    	x->add(z);

			    //	cout<<"weight "<<sqrt(pow(mTreeLambda,size))<<endl;

		      			}

		      		}
		      	}

		    return *x;

	 }

SVector&   NSDDkernel_FeatureGenerator::generate_vertex_feature_vector_DIVIDED(unsigned i, const GraphClass& aG , int dag_h, vector<SVector> & features_grouped_by_depth){
	//cout<<"start generation of feature vector.."<<endl;
			SVector* x = new SVector();
			features_grouped_by_depth= vector<SVector>(dag_h+1);
		/*	for(int i=0; i<dag_h;i++){
				features_grouped_by_depth.push_back(SVector(0));

			}*/
		//	cout<<"n of nodes "<<aG.VertexSize()<<endl;
		    assert(i< aG.VertexSize());
		    	map<pair<unsigned, unsigned>, int> oSrcDestMaptoDistance;
		    	map<unsigned, vector<unsigned> > MapNodetoParents;
		    	map<unsigned, vector<unsigned> > MapNodetoChildren;

		    	vector<unsigned> TopologicalSort;
		    	int maxLevel = 0;
		    	//aG. // codice calcolo features here
		      	aG.SingleVertexBoundedBreadthFirstVisitTree(i,dag_h, oSrcDestMaptoDistance,  MapNodetoParents,MapNodetoChildren, TopologicalSort, maxLevel);
		//    cout<<"MaxLevel: "<<maxLevel<<endl;
		      	// 	cout<<"Breadth first visit ended.."<<endl;
		      	// I have the DAG for the vertex i. Need topological sort.
		      	//map every node to him productions
		    	map<unsigned, vector<unsigned> > MapNodeToProductionsID;
		    	map<unsigned, vector<int> > MapNodetoFrequencies;

		    	map<unsigned, int > MapProductionIDtoSize;

		  //  	cout<<"topological sort size "<<TopologicalSort.size()<<endl;

		      	//maybe I meed the maximum depth (distance)
		      	for (int ii=TopologicalSort.size()-1; ii>=0; ii--){
		      		// cout<<ii<<endl;
		      		//calculate child size
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		      		int max_child_heigth=0;
		        	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {
		    			int child_vertex_id= vertex_adjacency_list[j];
		    			int child_heigth=MapNodeToProductionsID[child_vertex_id].size();
		    			if(child_heigth > max_child_heigth){
		    				max_child_heigth = child_heigth;
		    			}
		        	}

		      		for (int depth=0; depth<= max_child_heigth; ++depth){ //era maxLevel
		      			unsigned hash_subgraph_code = 1;
		      		//depth 0; only label
		      			if(depth==0){
		      		//		cout<<"depth=0"<<endl;
		      				unsigned enc= Radius0RootedGraphCanonicalFormEncoding(TopologicalSort[ii], aG);
		      		MapNodeToProductionsID[TopologicalSort[ii]].push_back(enc);
		      		// scorro i figli e creo etichetta tramite funzione di hash
		      		// depth > 0; the length of the children ID list
		      		//imposta valore feature a 1 (TODO lambda)
		      		//							level(o-mRadius)
			    	int frequency = 0;
			    	if(max_child_heigth==0){
		      		//leaf node
			    	frequency=  maxLevel - oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])];
			    	}
			    	SVector z;
		      		z.set(enc, (double)(frequency+1.0) * sqrt(mTreeLambda));
			    //	cout<<"Feature: "<<aG.GetVertexLabelConcatenated(TopologicalSort[ii])<<" freq:"<<(double)(frequency+1.0) * sqrt(mTreeLambda)<<" code: "<<enc<<endl;
			    	x->add(z);
			    	features_grouped_by_depth[depth].add(z);
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);
		      		MapProductionIDtoSize[enc]= 1; //sqrt(mTreeLambda);
		      			}
		      			else{
		      	//			cout<<"depth="<<depth<<endl;

		      			int size = 0;
		      		string encoding;
		      		encoding = aG.GetVertexLabelConcatenated(TopologicalSort[ii]);
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		    		vector<unsigned>  vertex_label_id_list;
		    		int min_freq_children =INT_MAX;
			    	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {

			    		unsigned child_vertex_id = vertex_adjacency_list[j];
			    		int size_map =  MapNodeToProductionsID[child_vertex_id].size();//min (depth-1) e dim
			    		unsigned child_hash = MapNodeToProductionsID[child_vertex_id][min(size_map,depth)-1];// id hash del figlio;
			    		int freq_child = MapNodetoFrequencies[child_vertex_id][min(size_map,depth)-1];
			    		if (freq_child < min_freq_children){
			    			min_freq_children = freq_child;
			    		}
			    		vertex_label_id_list.push_back(child_hash);
			    		size += MapProductionIDtoSize[child_hash];
			    	}

			    	// vertex_label_id_list contiene gli hash delle prod dei figli. Va ordinato e usato x generare l hash attuale
			    	sort(vertex_label_id_list.begin(), vertex_label_id_list.end());

			    	if (vertex_label_id_list.size() > 0){
			    		std::stringstream ss;
			    		ss <<":"<<vertex_label_id_list[0];
			    		encoding += ss.str() ;
			    	}//originale senza .
			    	for (unsigned i = 1; i < vertex_label_id_list.size(); i++){
			    		std::stringstream ss;
			    		ss << vertex_label_id_list[i];
			    		encoding += "." + ss.str() ;
			    	}
			    //DEBUG	cout<<"encoding "<<encoding<<endl;
			    	//calculate frequency

			    	hash_subgraph_code = HashFunc(encoding);
			    	MapNodeToProductionsID[TopologicalSort[ii]].push_back(hash_subgraph_code);
			    	size += 1; // current node
			    	MapProductionIDtoSize[hash_subgraph_code]= size;
			    	int frequency = min_freq_children;
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);

			    	SVector z;
			   // 	cout<<"Feature: "<<encoding<<" freq:"<<(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size))<<" code: "<<hash_subgraph_code<<endl;
			    	z.set(hash_subgraph_code,(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size)));
			    	x->add(z);
			    	features_grouped_by_depth[depth].add(z);
			    //	cout<<"weight "<<sqrt(pow(mTreeLambda,size))<<endl;

		      			}

		      		}
		      	}

		    return *x;

	 }

SVector&   NSDDkernel_FeatureGenerator::generate_vertex_feature_vector_DIVIDED_DISTANCE(unsigned i, const GraphClass& aG , int dag_h, vector<vector<SVector> > & features_grouped_by_depth){
	//cout<<"start generation of feature vector.."<<endl;
			SVector* x = new SVector();
			features_grouped_by_depth= vector<vector<SVector> >(dag_h+1);

		//	cout<<"n of nodes "<<aG.VertexSize()<<endl;
		    assert(i< aG.VertexSize());
		    	map<pair<unsigned, unsigned>, int> oSrcDestMaptoDistance;
		    	map<unsigned, vector<unsigned> > MapNodetoParents;
		    	map<unsigned, vector<unsigned> > MapNodetoChildren;

		    	vector<unsigned> TopologicalSort;
		    	int maxLevel = 0;
		    	//aG. // codice calcolo features here
		      	aG.SingleVertexBoundedBreadthFirstVisitTree(i,dag_h, oSrcDestMaptoDistance,  MapNodetoParents,MapNodetoChildren, TopologicalSort, maxLevel);
		//    cout<<"MaxLevel: "<<maxLevel<<endl;
				for(int t=0; t<maxLevel+1;t++){
					features_grouped_by_depth[t] =(vector<SVector>(dag_h+1));

				}

		      	// 	cout<<"Breadth first visit ended.."<<endl;
		      	// I have the DAG for the vertex i. Need topological sort.
		      	//map every node to him productions
		    	map<unsigned, vector<unsigned> > MapNodeToProductionsID;
		    	map<unsigned, vector<int> > MapNodetoFrequencies;

		    	map<unsigned, int > MapProductionIDtoSize;

		  //  	cout<<"topological sort size "<<TopologicalSort.size()<<endl;

		      	//maybe I meed the maximum depth (distance)
		      	for (int ii=TopologicalSort.size()-1; ii>=0; ii--){
		      		// cout<<ii<<endl;
		      		//calculate child size
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		      		int max_child_heigth=0;
		        	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {
		    			int child_vertex_id= vertex_adjacency_list[j];
		    			int child_heigth=MapNodeToProductionsID[child_vertex_id].size();
		    			if(child_heigth > max_child_heigth){
		    				max_child_heigth = child_heigth;
		    			}
		        	}

		      		for (int depth=0; depth<= max_child_heigth; ++depth){ //era maxLevel
		      			unsigned hash_subgraph_code = 1;
		      		//depth 0; only label
		      			if(depth==0){
		      		//		cout<<"depth=0"<<endl;
		      				unsigned enc= Radius0RootedGraphCanonicalFormEncoding(TopologicalSort[ii], aG);
		      		MapNodeToProductionsID[TopologicalSort[ii]].push_back(enc);
		      		// scorro i figli e creo etichetta tramite funzione di hash
		      		// depth > 0; the length of the children ID list
		      		//imposta valore feature a 1 (TODO lambda)
		      		//							level(o-mRadius)
			    	int frequency = 0;
			    	if(max_child_heigth==0){
		      		//leaf node
			    	frequency=  maxLevel - oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])];
			    	}
			    	SVector z;
		      		z.set(enc, (double)(frequency+1.0) * sqrt(mTreeLambda));
			    //	cout<<"Feature: "<<aG.GetVertexLabelConcatenated(TopologicalSort[ii])<<" freq:"<<(double)(frequency+1.0) * sqrt(mTreeLambda)<<" code: "<<enc<<endl;
			    	x->add(z);
			    	features_grouped_by_depth[depth][oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])]].add(z);
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);
		      		MapProductionIDtoSize[enc]= 1; //sqrt(mTreeLambda);
		      			}
		      			else{
		      	//			cout<<"depth="<<depth<<endl;

		      			int size = 0;
		      		string encoding;
		      		encoding = aG.GetVertexLabelConcatenated(TopologicalSort[ii]);
			    	vector<unsigned> vertex_adjacency_list = MapNodetoChildren[TopologicalSort[ii]];
		    		vector<unsigned>  vertex_label_id_list;
		    		int min_freq_children =INT_MAX;
			    	for (unsigned j = 0; j < vertex_adjacency_list.size(); ++j) {

			    		unsigned child_vertex_id = vertex_adjacency_list[j];
			    		int size_map =  MapNodeToProductionsID[child_vertex_id].size();//min (depth-1) e dim
			    		unsigned child_hash = MapNodeToProductionsID[child_vertex_id][min(size_map,depth)-1];// id hash del figlio;
			    		int freq_child = MapNodetoFrequencies[child_vertex_id][min(size_map,depth)-1];
			    		if (freq_child < min_freq_children){
			    			min_freq_children = freq_child;
			    		}
			    		vertex_label_id_list.push_back(child_hash);
			    		size += MapProductionIDtoSize[child_hash];
			    	}

			    	// vertex_label_id_list contiene gli hash delle prod dei figli. Va ordinato e usato x generare l hash attuale
			    	sort(vertex_label_id_list.begin(), vertex_label_id_list.end());

			    	if (vertex_label_id_list.size() > 0){
			    		std::stringstream ss;
			    		ss <<":"<<vertex_label_id_list[0];
			    		encoding += ss.str() ;
			    	}//originale senza .
			    	for (unsigned id = 1; id < vertex_label_id_list.size(); id++){
			    		std::stringstream ss;
			    		ss << vertex_label_id_list[id];
			    		encoding += "." + ss.str() ;
			    	}
			    //DEBUG	cout<<"encoding "<<encoding<<endl;
			    	//calculate frequency

			    	hash_subgraph_code = HashFunc(encoding);
			    	MapNodeToProductionsID[TopologicalSort[ii]].push_back(hash_subgraph_code);
			    	size += 1; // current node
			    	MapProductionIDtoSize[hash_subgraph_code]= size;
			    	int frequency = min_freq_children;
			    	MapNodetoFrequencies[TopologicalSort[ii]].push_back(frequency);

			    	SVector z;
			   // 	cout<<"Feature: "<<encoding<<" freq:"<<(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size))<<" code: "<<hash_subgraph_code<<endl;
			    	z.set(hash_subgraph_code,(double)(frequency+1.0) * sqrt(pow(mTreeLambda,size)));
			    	x->add(z);
			    	features_grouped_by_depth[depth][oSrcDestMaptoDistance[make_pair(i,TopologicalSort[ii])]].add(z); //TODO distance from root
			    //	cout<<"weight "<<sqrt(pow(mTreeLambda,size))<<endl;

		      			}

		      		}
		      	}

		    return *x;

	 }

//Kernel su dati con astrazione (nodi di relazione)
ANSDDkernel_FeatureGenerator::ANSDDkernel_FeatureGenerator(const std::string& id) :
		NSDDkernel_FeatureGenerator(id) {
		//new_flag(&mTreeLambda, "mTreeLambda", "(double)\n tree_lambda parameter for DDK kernel");
}

void ANSDDkernel_FeatureGenerator::generate_feature_vector(const GraphClass& aG, SVector& x, const vector<unsigned>& aFirstEndpointList) {
	//cout<<"generate_vertex_feature_vector.... OK for abstract"<<endl;
	vector<unsigned> first_endpoint_list = aFirstEndpointList;
	GetFirstEndpoints(aG, first_endpoint_list);
	if (first_endpoint_list.size() == 0)
		throw std::logic_error("ERROR6: Something went wrong: cannot generate features over an empty set of first endpoints!");
	int horizon = max(mDistance, mRadius);
	aG.ComputePairwiseDistanceInformation(horizon, first_endpoint_list);
	if (aG.Check() == false)
		throw logic_error("ERROR11: the graph data structure is not sound and it has not passed the checkup procedure"); //check graph data structure soundness

	InitFeatureCache(aG, mRadiusTwo);
	for (unsigned i = 0; i < first_endpoint_list.size(); i++) {
		SVector z;
		unsigned src_id = first_endpoint_list[i];
		//InitFeatureCache(aG, mRadiusTwo);

		if (aG.GetVertexViewPoint(src_id) && aG.GetVertexKernelPoint(src_id) && aG.GetVertexAlive(src_id)) { //proceed to extract features only if the *src* vertex is a kernel point and is alive
			//for (unsigned r = 0; r <= mRadius; r++) {
			//	for (unsigned d = 0; d <= mDistance; d++) {
				//	GenerateVertexFeatures(src_id, aG, r, d, z);
			GenerateVertexFeatures(src_id, aG, mRadius, mDistance, z);
				//}
			//}
		} else if (aG.GetVertexAbstraction(src_id) && aG.GetVertexAlive(src_id)) {
			//for (unsigned r = 0; r <= mRadius; r++) {
			//	for (unsigned d = 0; d <= mDistance; d++) {
				//	GenerateAbstractVertexFeatures(src_id, aG, r, d, z);
			GenerateAbstractVertexFeatures(src_id, aG, mRadius, mDistance, z);

			//	}
			//}
		}
		if (mMinKernel)
			ConvertSparseVectorToMinFeatureVector(z);
		//x_list.push_back(z);
		x.add(z);
	}
	if (mDebugVerbosity > 0) {

		OutputFeatureMap(cout);
		aG.Output(cout);
	}
}
void ANSDDkernel_FeatureGenerator::GenerateVertexFeatures(unsigned aRootVertexIndex, const GraphClass& aG, unsigned aRadius, unsigned aDistance, SVector& x) {
	x= generateVertexFeatures_DISTANCE(aRootVertexIndex, aG ,aRadius);
}

SVector&   ANSDDkernel_FeatureGenerator::generateVertexFeatures_DISTANCE(unsigned i, const GraphClass& aG , int dag_h){
   // test rNSPDK 1/2 d=1/2
	GraphClass ag2 = aG;
	SVector* out = new SVector();
//	cout<<"Generate feature vector core"<<endl;
	//	cout<<"i:"<<i<<endl;

		//SVector DD= generate_vertex_feature_vector(i,ag2 ,dag_h);
		vector< vector< SVector> > features; //(dag_h+1);
		SVector DD= generate_vertex_feature_vector_DIVIDED_DISTANCE(i,ag2 ,dag_h,features);
		//cout<<features.size();
	//	out->add(DD);
		//NSPDK
	//	cout<<"start generation of NSPKD feature vector.. mDistance"<<mDistance<<endl;

		//NSPDK_FeatureGenerator::GenerateVertexFeatures(i,aG,NSPDK);
	//	NSPDK_FeatureGenerator::generate_feature_vector(aG,NSPDK);
		vector<unsigned> first_endpoint_list= vector<unsigned>();
		first_endpoint_list.push_back(i);
			//GetFirstEndpoints(aG, first_endpoint_list);

		int horizon=max(mDistance, mRadiusTwo); //   max(mDistance, mRadius);
			aG.ComputePairwiseDistanceInformation(horizon, first_endpoint_list);
			if (aG.Check() == false)
				throw logic_error("ERROR10: the graph data structure is not sound and it has not passed the checkup procedure"); //check graph data structure soundness

			InitFeatureCache(aG, mRadiusTwo );
			for (unsigned r = 0; r <= mRadius ; r++) { //max(1.0,mRadius / 2.0)
				for (unsigned d = 0; d <= mDistance; d++) { // era 0
					for (unsigned r2 = 0; r2 <= mRadiusTwo ; r2++) {
					SVector z;
					for (unsigned i = 0; i < first_endpoint_list.size(); i++) {
						unsigned src_id = first_endpoint_list[i];
						if (aG.GetVertexViewPoint(src_id) && aG.GetVertexKernelPoint(src_id) && aG.GetVertexAlive(src_id)) { //proceed to extract features only if the *src* vertex is a kernel point and is alive
							SVector zv;

							vector<unsigned> endpoint_list(5);
							endpoint_list[0] = r2;
							endpoint_list[1] = d;

						//	unsigned src_code = GenerateVertexNeighbourhoodHashCode(aSrcID, aG, aRadius);
				//	TODO	for(unsigned src_code in DD)

							vector<unsigned> dest_id_list = aG.GetFixedDistanceVertexIDList(i, d);
							for (unsigned dest_j = 0; dest_j < dest_id_list.size(); dest_j++) {
								unsigned dest_id = dest_id_list[dest_j];
								unsigned dest_code = 0;
								if (aG.GetVertexKernelPoint(dest_id) && aG.GetVertexAlive(dest_id)) { //proceed to extract features only if the *dest* vertex is a kernel point and is alive
									dest_code = GenerateVertexNeighbourhoodHashCode(dest_id, aG, r2); //
									// first is the DDK feature, second NSPDK feature
						//  for(const SVector::Pair *p = DD; p->i>=0; p++) {
									for(int distance=0; distance<features[r].size(); distance++){
										endpoint_list[4]=distance; //from root in DD
						 for(const SVector::Pair *p = features[r][distance]; p->i>=0; p++) {

									endpoint_list[2] = p->i;
										//TODO scorrere le feature di DD
										endpoint_list[3] = dest_code;
										//TEST output
								//		cout<<"R "<<endpoint_list[0]<<" d "<<endpoint_list[1]<<" DD "<<endpoint_list[2]<<" NSPDK "<<endpoint_list[3]<<" dRoot "<<endpoint_list[4]<<endl;
									unsigned code = HashFunc(endpoint_list, mHashBitMask);

									SVector z;
									z.set(code, p->v);
									zv.add(z);
								}
								}

								}
							}

							z.add(zv);
						}
						}

					if (mNormalization)
						z.normalize();
				out->add(z);
				} //d
			} //r
			} //r2

	//	cout<<"features size DD:"<<DD.sparse_size()<<" out:"<<out->sparse_size()<<endl;
		features.clear();

	return *out;
}

void ANSDDkernel_FeatureGenerator::GenerateAbstractVertexFeatures(unsigned aSrcID, const GraphClass& aG, unsigned aRadius, unsigned aDistance, SVector& x) {
	//cout<<"ABSTRACT FEATURES"<<endl;
	vector<unsigned> endpoint_list(5);
	for (unsigned r = 0; r <= mRadius ; r++) { //max(1.0,mRadius / 2.0)
					for (unsigned d = 0; d <= mDistance; d++) { // era 0
						for (unsigned r2 = 0; r2 <= mRadiusTwo ; r2++) { //max(1.0,mRadius / 2.0)

	endpoint_list[0] = r;
	endpoint_list[1] = d;

	//ensure that the src vertex is of the abstraction type
	if (aG.GetVertexAbstraction(aSrcID) == false)
		throw std::logic_error("ERROR2: Something went wrong: expecting an abstraction vertex, but abstraction test fails.");
	//extract all adjacent vertices and partition them into part_of (lower level) and abstraction_of (upper level)
	vector<unsigned> part_of_list;
	vector<unsigned> abstraction_of_list;

	vector<unsigned> vertex_adjacency_list = aG.GetVertexAdjacentList(aSrcID);
	vector<unsigned> edge_adjacency_list = aG.GetEdgeAdjacentList(aSrcID);
	if (vertex_adjacency_list.size() != edge_adjacency_list.size())
		throw std::logic_error("ERROR3: Something went wrong: expecting vertex adjacency list to be the same size as the edge adjacency list.");
	if (vertex_adjacency_list.size() == 0)
		throw std::logic_error("ERROR3b: Something went wrong: expecting a non empty vertex adjacency list for each abstract vertex.");
	for (unsigned i = 0; i < vertex_adjacency_list.size(); ++i) {
		unsigned child_vertex_id = vertex_adjacency_list[i];
		unsigned child_edge_id = edge_adjacency_list[i];
		if (aG.GetEdgePartOf(child_edge_id))
			part_of_list.push_back(child_vertex_id);
		if (aG.GetEdgeAbstractionOf(child_edge_id))
			abstraction_of_list.push_back(child_vertex_id);
	}
	//----------------------------

	//----------------------------------------
						//starting from each upper level vertex find all vertices at distance aDistance
	if (abstraction_of_list.size() == 0)
		throw std::logic_error("ERROR5: Something went wrong: expecting a non empty abstraction_of list.");
	set<unsigned> abstraction_of_set;
	for (unsigned i = 0; i < abstraction_of_list.size(); ++i) {
		unsigned v = abstraction_of_list[i];
		vector<unsigned> dest_id_list = aG.GetFixedDistanceVertexIDList(v, d);
		//-----------?????????????????????????????
	//	abstraction_of_set.insert(abstraction_of_list.begin(), abstraction_of_list.end());
		abstraction_of_set.insert(dest_id_list.begin(), dest_id_list.end());

	}
	//starting from each lower level vertex find all vertices at distance aDistance
	if (part_of_list.size() == 0)
		throw std::logic_error("ERROR4: Something went wrong: expecting a non empty part_of list.");
	set<unsigned> part_of_set;
	for (unsigned i = 0; i < part_of_list.size(); ++i) {
		unsigned v = part_of_list[i];
		vector<unsigned> dest_id_list = aG.GetFixedDistanceVertexIDList(v, d);
		//-----------?????????????????????????????
		part_of_set.insert(dest_id_list.begin(), dest_id_list.end());
	}
	//make all possible pairs of distant lower level vertices with distant upper level vertices
	for (set<unsigned>::iterator it = part_of_set.begin(); it != part_of_set.end(); ++it) {
		unsigned part_of_id = *it;

		//TODO modifica con mio kernel
		vector< vector< SVector> > features; //(dag_h+1);
		SVector DD= generate_vertex_feature_vector_DIVIDED_DISTANCE(part_of_id, aG ,aRadius,features);
		//SVector DD= generate_vertex_feature_vector_DIVIDED(part_of_id, aG ,aRadius,features);

		//For every feature in DD, by height and distance from the root
		for(int distance=0; distance<features[r].size(); distance++){
			endpoint_list[4]=distance; //from root in DD
			for(const SVector::Pair *p = features[r][distance]; p->i>=0; p++) {

			endpoint_list[2] = p->i;
			//TODO scorrere le feature di DD
				//TEST output
			//		cout<<"R "<<endpoint_list[0]<<" d "<<endpoint_list[1]<<" DD "<<endpoint_list[2]<<" NSPDK "<<endpoint_list[3]<<" dRoot "<<endpoint_list[4]<<endl;

			//
		for (set<unsigned>::iterator jt = abstraction_of_set.begin(); jt != abstraction_of_set.end(); ++jt) {
			unsigned abstraction_of_id = *jt;
			//build features with one neighborhood graph signature from the lower level and one from the upper
			unsigned abstraction_of_code = GenerateVertexNeighbourhoodHashCode(abstraction_of_id, aG, r2);
			endpoint_list[3] = abstraction_of_code;
			unsigned code = HashFunc(endpoint_list, mHashBitMask);
			if (mDebugVerbosity > 0)
				mDebugInfo.StoreFeatureCodeToFeatureInfo(code, endpoint_list);
			SVector z;
			z.set(code, 1);
			x.add(z);
		}
	}
		}
	}

	//i have to repeat for both sides for ddk
	//make all possible pairs of distant lower level vertices with distant upper level vertices
	for (set<unsigned>::iterator it = part_of_set.begin(); it != part_of_set.end(); ++it) {
		unsigned part_of_id = *it;

		//TODO modifica con mio kernel
		vector< vector< SVector> > features; //(dag_h+1);
		SVector DD= generate_vertex_feature_vector_DIVIDED_DISTANCE(part_of_id, aG ,aRadius,features);

			//For every feature in DD, by height and distance from the root
			for(int distance=0; distance<features[r].size(); distance++){
				endpoint_list[4]=distance; //from root in DD
				for(const SVector::Pair *p = features[r][distance]; p->i>=0; p++) {

				endpoint_list[2] = p->i;

		unsigned part_of_code = GenerateVertexNeighbourhoodHashCode(part_of_id, aG, aRadius);
		endpoint_list[2] = part_of_code;
		for (set<unsigned>::iterator jt = abstraction_of_set.begin(); jt != abstraction_of_set.end(); ++jt) {
			unsigned abstraction_of_id = *jt;
			//build features with one neighborhood graph signature from the lower level and one from the upper
			unsigned abstraction_of_code = GenerateVertexNeighbourhoodHashCode(abstraction_of_id, aG, aRadius);
			endpoint_list[3] = abstraction_of_code;
			unsigned code = HashFunc(endpoint_list, mHashBitMask);
			if (mDebugVerbosity > 0)
				mDebugInfo.StoreFeatureCodeToFeatureInfo(code, endpoint_list);
			SVector z;
			z.set(code, 1);
			x.add(z);
		}
	}
	}

}
					}
	}
}
}


#include <frovedis.hpp>
#include <frovedis/matrix/sliced_vector.hpp>
#include <frovedis/matrix/sliced_matrix.hpp>
#include <frovedis/matrix/pblas_wrapper.hpp>

#define BOOST_TEST_MODULE FrovedisTest
#include <boost/test/unit_test.hpp>

using namespace frovedis;
using namespace std;

BOOST_AUTO_TEST_CASE( frovedis_test )
{
    int argc = 1;
    char** argv = NULL;
    use_frovedis use(argc, argv);

    auto bm = make_blockcyclic_matrix_load<float> ("./sample_6x6");

    // creating slice matrix from blockcyclic matrix (error, invalid range)
    bool error_in_slice_matrix_creation = false;
    sliced_blockcyclic_matrix<float> sm;
    try {
      sm = make_sliced_blockcyclic_matrix<float>(bm,1,1,6,6);
    }
    catch (std::exception& e) {
      error_in_slice_matrix_creation = true;
    }

    // creating slice matrix from blockcyclic matrix (OK)
    sm = make_sliced_blockcyclic_matrix<float>(bm,1,1,4,4);

    // creating slice vectors from slice matrix
    bool error_in_row_vector_creation = false;
    sliced_blockcyclic_vector<float> sv;
    try {
      sv = make_row_vector(sm,4); // error, invalid row-id
    }
    catch (std::exception& e) {
      error_in_row_vector_creation = true;
    }

    // confirming if above expected exceptions occur
    BOOST_CHECK (error_in_slice_matrix_creation);
    BOOST_CHECK (error_in_row_vector_creation);
}