/*
anharmonic_core.cpp

Copyright (c) 2014, 2015, 2016 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 "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()
{
    im = std::complex<double>(0.0, 1.0);
    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;
    exp_phase = nullptr;
    exp_phase3 = nullptr;
    phi3_reciprocal = nullptr;
    phi4_reciprocal = nullptr;
}

void AnharmonicCore::deallocate_variables()
{
    if (relvec_v3) {
        memory->deallocate(relvec_v3);
    }
    if (relvec_v4) {
        memory->deallocate(relvec_v4);
    }
    if (invmass_v3) {
        memory->deallocate(invmass_v3);
    }
    if (invmass_v4) {
        memory->deallocate(invmass_v4);
    }
    if (evec_index_v3) {
        memory->deallocate(evec_index_v3);
    }
    if (evec_index_v4) {
        memory->deallocate(evec_index_v4);
    }
    if (fcs_group_v3) {
        memory->deallocate(fcs_group_v3);
    }
    if (fcs_group_v4) {
        memory->deallocate(fcs_group_v4);
    }
    if (exp_phase) {
        memory->deallocate(exp_phase);
    }
    if (exp_phase3) {
        memory->deallocate(exp_phase3);
    }
    if (phi3_reciprocal) {
        memory->deallocate(phi3_reciprocal);
    }
    if (phi4_reciprocal) {
        memory->deallocate(phi4_reciprocal);
    }
}

void AnharmonicCore::setup()
{
    if (fcs_phonon->maxorder >= 2) setup_cubic();
    if (fcs_phonon->maxorder >= 3) setup_quartic();

    sym_permutation = true;
    use_tuned_ver = true;

    if (!mode_analysis->calc_fstate_k && kpoint->kpoint_mode == 2) {
        int nk_tmp[3];
        nk_tmp[0] = kpoint->nkx;
        nk_tmp[1] = kpoint->nky;
        nk_tmp[2] = kpoint->nkz;
        store_exponential_for_acceleration(nk_tmp,
                                           nk_represent,
                                           exp_phase,
                                           exp_phase3);
    }
}

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

    double vec[3];
    double **xshift_s;

    std::vector<unsigned int> atm_super, atm_prim;
    std::vector<unsigned int> xyz;
    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 (int k = 0; k < 3; ++k) {
                mat_convert[i][j] += system->rlavec_p[i][k] * system->lavec_s_anharm[k][j];
            }
        }
    }

    memory->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);
            }
        }
    }

    unsigned int icount = 0;

    for (const auto &it : fcs_in) {

        atm_super.clear();
        atm_prim.clear();
        xyz.clear();
        cells.clear();

        for (i = 0; i < it.pairs.size(); ++i) {
            auto atm_p = it.pairs[i].index / 3;
            const auto tran_tmp = it.pairs[i].tran;
            auto atm_s = system->map_p2s_anharm[atm_p][tran_tmp];

            atm_prim.push_back(atm_p);
            atm_super.push_back(atm_s);
            cells.push_back(it.pairs[i].cell_s);
        }

        for (i = 0; i < N - 1; ++i) {

            for (j = 0; j < 3; ++j) {
                vec[j] = system->xr_s_anharm[atm_super[i + 1]][j] + xshift_s[cells[i + 1]][j]
                         - system->xr_s_anharm[system->map_p2s_anharm[atm_prim[i + 1]][0]][j];
            }

            rotvec(vec, vec, mat_convert);

            for (j = 0; j < 3; ++j) {
                vec_out[icount][i][j] = vec[j];
            }
        }
        ++icount;
    }
    memory->deallocate(xshift_s);
}

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];
            }
        }
    }

    memory->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;
        }
    }

    memory->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));
        }
    }

    memory->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,
              dynamical->eval_phonon,
              dynamical->evec_phonon);
}

std::complex<double> AnharmonicCore::V4(const unsigned int ks[4])
{
    return V4(ks,
              dynamical->eval_phonon,
              dynamical->evec_phonon);
}

std::complex<double> AnharmonicCore::Phi3(const unsigned int ks[3])
{
    return Phi3(ks,
                dynamical->eval_phonon,
                dynamical->evec_phonon);
}

std::complex<double> AnharmonicCore::Phi4(const unsigned int ks[4])
{
    return Phi4(ks,
                dynamical->eval_phonon,
                dynamical->evec_phonon);
}

std::complex<double> AnharmonicCore::V3(const unsigned int ks[3],
                                        double **eval_phonon,
                                        std::complex<double> ***evec_phonon)
{
    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_phonon[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;

    if (kn[1] != kindex_phi3_stored[0] || kn[2] != kindex_phi3_stored[1]) {
        calc_phi3_reciprocal(kn[1], kn[2], 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_phonon[kn[0]][sn[0]][evec_index_v3[i][0]]
              * evec_phonon[kn[1]][sn[1]][evec_index_v3[i][1]]
              * evec_phonon[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],
                                          double **eval_phonon,
                                          std::complex<double> ***evec_phonon)
{
    int i;
    unsigned int kn[3], sn[3];
    int 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_phonon[kn[i]][sn[i]];
    }

    if (kn[1] != kindex_phi3_stored[0] || kn[2] != kindex_phi3_stored[1]) {
        calc_phi3_reciprocal(kn[1], kn[2], 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_phonon[kn[0]][sn[0]][evec_index_v3[i][0]]
              * evec_phonon[kn[1]][sn[1]][evec_index_v3[i][1]]
              * evec_phonon[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 unsigned int ik1,
                                          const unsigned int ik2,
                                          std::complex<double> *ret)
{
    int i, j;
    unsigned int iloc;
    double phase;
    const auto dnk_represent = static_cast<double>(nk_represent) / (2.0 * pi);
    std::complex<double> ret_in;
    unsigned int nsize_group;

    unsigned int ielem = 0;

    if (use_tuned_ver) {

        if (tune_type == 0) {

#pragma omp parallel for private(ret_in, nsize_group, j, phase, iloc)
            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] * kpoint->xk[ik1][0]
                            + relvec_v3[i][j].vecs[0][1] * kpoint->xk[ik1][1]
                            + relvec_v3[i][j].vecs[0][2] * kpoint->xk[ik1][2]
                            + relvec_v3[i][j].vecs[1][0] * kpoint->xk[ik2][0]
                            + relvec_v3[i][j].vecs[1][1] * kpoint->xk[ik2][1]
                            + relvec_v3[i][j].vecs[1][2] * kpoint->xk[ik2][2];

                    unsigned int iloc = nint(phase * dnk_represent) % nk_represent + nk_represent - 1;
                    ret_in += fcs_group_v3[i][j] * exp_phase[iloc];
                }
                ret[i] = ret_in;
            }

        } else if (tune_type == 1) {

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

            int loc[3];
            double phase3[3];
            const auto inv2pi = 1.0 / (2.0 * pi);

#pragma omp parallel for private(ret_in, nsize_group, j, phase3, loc)
            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] * kpoint->xk[ik1][ii]
                                  + relvec_v3[i][j].vecs[1][ii] * kpoint->xk[ik2][ii];

                        loc[ii] = nint(phase3[ii] * dnk[ii] * inv2pi) % nk_grid[ii] + nk_grid[ii] - 1;
                    }

                    ret_in += fcs_group_v3[i][j] * exp_phase3[loc[0]][loc[1]][loc[2]];
                }
                ret[i] = ret_in;
            }
        }

    } else {
        // Original version
#pragma omp parallel for private(ret_in, nsize_group, 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] * kpoint->xk[ik1][0]
                          + relvec_v3[i][j].vecs[0][1] * kpoint->xk[ik1][1]
                          + relvec_v3[i][j].vecs[0][2] * kpoint->xk[ik1][2]
                          + relvec_v3[i][j].vecs[1][0] * kpoint->xk[ik2][0]
                          + relvec_v3[i][j].vecs[1][1] * kpoint->xk[ik2][1]
                          + relvec_v3[i][j].vecs[1][2] * kpoint->xk[ik2][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],
                                        double **eval_phonon,
                                        std::complex<double> ***evec_phonon)
{
    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_phonon[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(kn[1],
                             kn[2],
                             kn[3],
                             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_phonon[kn[0]][sn[0]][evec_index_v4[i][0]]
              * evec_phonon[kn[1]][sn[1]][evec_index_v4[i][1]]
              * evec_phonon[kn[2]][sn[2]][evec_index_v4[i][2]]
              * evec_phonon[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],
                                        double **eval_phonon,
                                        std::complex<double> ***evec_phonon)
{
    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_phonon[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(kn[1],
                             kn[2],
                             kn[3],
                             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_phonon[kn[0]][sn[0]][evec_index_v4[i][0]]
              * evec_phonon[kn[1]][sn[1]][evec_index_v4[i][1]]
              * evec_phonon[kn[2]][sn[2]][evec_index_v4[i][2]]
              * evec_phonon[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 unsigned int ik1,
                                          const unsigned int ik2,
                                          const unsigned int ik3,
                                          std::complex<double> *ret)
{
    int i, j;
    unsigned int iloc;
    double phase;
    const auto dnk_represent = static_cast<double>(nk_represent) / (2.0 * pi);
    std::complex<double> ret_in;
    unsigned int nsize_group;

    unsigned int ielem = 0;

    if (use_tuned_ver) {

        if (tune_type == 0) {

#pragma omp parallel for private(ret_in, nsize_group, j, phase, iloc)
            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] * kpoint->xk[ik1][0]
                            + relvec_v4[i][j].vecs[0][1] * kpoint->xk[ik1][1]
                            + relvec_v4[i][j].vecs[0][2] * kpoint->xk[ik1][2]
                            + relvec_v4[i][j].vecs[1][0] * kpoint->xk[ik2][0]
                            + relvec_v4[i][j].vecs[1][1] * kpoint->xk[ik2][1]
                            + relvec_v4[i][j].vecs[1][2] * kpoint->xk[ik2][2]
                            + relvec_v4[i][j].vecs[2][0] * kpoint->xk[ik3][0]
                            + relvec_v4[i][j].vecs[2][1] * kpoint->xk[ik3][1]
                            + relvec_v4[i][j].vecs[2][2] * kpoint->xk[ik3][2];

                    unsigned int iloc = nint(phase * dnk_represent) % nk_represent + nk_represent - 1;
                    ret_in += fcs_group_v4[i][j] * exp_phase[iloc];
                }
                ret[i] = ret_in;
            }

        } else if (tune_type == 1) {

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

            int loc[3];
            double phase3[3];
            const auto inv2pi = 1.0 / (2.0 * pi);

#pragma omp parallel for private(ret_in, nsize_group, j, phase3, loc)
            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] * kpoint->xk[ik1][ii]
                                  + relvec_v4[i][j].vecs[1][ii] * kpoint->xk[ik2][ii]
                                  + relvec_v4[i][j].vecs[2][ii] * kpoint->xk[ik3][ii];

                        loc[ii] = nint(phase3[ii] * dnk[ii] * inv2pi) % nk_grid[ii] + nk_grid[ii] - 1;
                    }

                    ret_in += fcs_group_v4[i][j] * exp_phase3[loc[0]][loc[1]][loc[2]];
                }
                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] * kpoint->xk[ik1][0]
                          + relvec_v4[i][j].vecs[0][1] * kpoint->xk[ik1][1]
                          + relvec_v4[i][j].vecs[0][2] * kpoint->xk[ik1][2]
                          + relvec_v4[i][j].vecs[1][0] * kpoint->xk[ik2][0]
                          + relvec_v4[i][j].vecs[1][1] * kpoint->xk[ik2][1]
                          + relvec_v4[i][j].vecs[1][2] * kpoint->xk[ik2][2]
                          + relvec_v4[i][j].vecs[2][0] * kpoint->xk[ik3][0]
                          + relvec_v4[i][j].vecs[2][1] * kpoint->xk[ik3][1]
                          + relvec_v4[i][j].vecs[2][2] * kpoint->xk[ik3][2];

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

std::complex<double> AnharmonicCore::V3_mode(int mode,
                                             double *xk2,
                                             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 N,
                                           double *T,
                                           const double omega,
                                           const unsigned int ik_in,
                                           const unsigned int snum,
                                           double *ret)
{
    // This function returns the imaginary part of phonon self-energy
    // for the given frequency omega.
    // Lorentzian or Gaussian smearing will be used.
    // This version employs the crystal symmetry to reduce the computational cost

    const int nk = kpoint->nk;
    const int 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 < N; ++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;

    kpoint->get_unique_triplet_k(ik_in,
                                 use_triplet_symmetry,
                                 sym_permutation,
                                 triplet);

    const int npair_uniq = triplet.size();

    memory->allocate(v3_arr, npair_uniq, ns * ns);
    memory->allocate(delta_arr, npair_uniq, ns * ns, 2);

    const int knum = kpoint->kpoint_irred_all[ik_in][0].knum;
    const int knum_minus = kpoint->knum_minus[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 + snum;

        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] = dynamical->eval_phonon[k1][is];

            for (js = 0; js < ns; ++js) {
                arr[2] = ns * k2 + js;
                omega_inner[1] = dynamical->eval_phonon[k2][js];

                if (integration->ismear == 0) {
                    delta_arr[ik][ns * is + js][0]
                            = delta_lorentz(omega - omega_inner[0] - omega_inner[1], epsilon)
                              - delta_lorentz(omega + omega_inner[0] + omega_inner[1], epsilon);
                    delta_arr[ik][ns * is + js][1]
                            = delta_lorentz(omega - omega_inner[0] + omega_inner[1], epsilon)
                              - delta_lorentz(omega + omega_inner[0] - omega_inner[1], epsilon);
                } else if (integration->ismear == 1) {
                    delta_arr[ik][ns * is + js][0]
                            = delta_gauss(omega - omega_inner[0] - omega_inner[1], epsilon)
                              - delta_gauss(omega + omega_inner[0] + omega_inner[1], epsilon);
                    delta_arr[ik][ns * is + js][1]
                            = delta_gauss(omega - omega_inner[0] + omega_inner[1], epsilon)
                              - delta_gauss(omega + 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 + snum;
            arr[1] = ns * k1 + is;
            arr[2] = ns * k2 + js;

            v3_arr[ik][ib] = std::norm(V3(arr,
                                          dynamical->eval_phonon,
                                          dynamical->evec_phonon)) * multi;
        }
    }

    for (i = 0; i < N; ++i) {
        T_tmp = T[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] = dynamical->eval_phonon[k1][is];

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

                    omega_inner[1] = dynamical->eval_phonon[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;
    }

    memory->deallocate(v3_arr);
    memory->deallocate(delta_arr);
    triplet.clear();

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

void AnharmonicCore::calc_damping_tetrahedron(const unsigned int N,
                                              double *T,
                                              const double omega,
                                              const unsigned int ik_in,
                                              const unsigned int snum,
                                              double *ret)
{
    // This function returns the imaginary part of phonon self-energy
    // for the given frequency omega.
    // Tetrahedron method will be used.
    // This version employs the crystal symmetry to reduce the computational cost

    const int nk = kpoint->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;

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

    std::vector<KsListGroup> triplet;

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

    kpoint->get_unique_triplet_k(ik_in,
                                 use_triplet_symmetry,
                                 sym_permutation,
                                 triplet);

    const auto npair_uniq = triplet.size();

    memory->allocate(v3_arr, npair_uniq, ns2);
    memory->allocate(delta_arr, npair_uniq, ns2, 2);

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

    memory->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
    {
        memory->allocate(energy_tmp, 3, nk);
        memory->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] = kpoint->xk[knum][i] - kpoint->xk[k1][i];

                k2 = kpoint->get_knum(xk_tmp[0], xk_tmp[1], xk_tmp[2]);

                energy_tmp[0][k1] = dynamical->eval_phonon[k1][is] + dynamical->eval_phonon[k2][js];
                energy_tmp[1][k1] = dynamical->eval_phonon[k1][is] - dynamical->eval_phonon[k2][js];
                energy_tmp[2][k1] = -energy_tmp[1][k1];
            }

            for (i = 0; i < 3; ++i) {
                integration->calc_weight_tetrahedron(nk,
                                                     kmap_identity,
                                                     weight_tetra[i],
                                                     energy_tmp[i],
                                                     omega);
            }

            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];
            }
        }

        memory->deallocate(energy_tmp);
        memory->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 + snum;
                arr[1] = ns * k1 + is;
                arr[2] = ns * k2 + js;

                v3_arr[ik][ib] = std::norm(V3(arr,
                                              dynamical->eval_phonon,
                                              dynamical->evec_phonon)) * multi;

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

    for (i = 0; i < N; ++i) {
        T_tmp = T[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] = dynamical->eval_phonon[k1][is];

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

                    omega_inner[1] = dynamical->eval_phonon[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;
    }

    memory->deallocate(v3_arr);
    memory->deallocate(delta_arr);
    memory->deallocate(kmap_identity);

    for (i = 0; i < N; ++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);

    memory->allocate(invmass_v3, ngroup_v3);
    memory->allocate(evec_index_v3, ngroup_v3, 3);
    memory->allocate(relvec_v3, ngroup_v3);
    memory->allocate(phi3_reciprocal, ngroup_v3);

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

    memory->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();
    }

    memory->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);

    memory->allocate(invmass_v4, ngroup_v4);
    memory->allocate(evec_index_v4, ngroup_v4, 4);
    memory->allocate(relvec_v4, ngroup_v4);
    memory->allocate(phi4_reciprocal, ngroup_v4);

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

    memory->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();
    }

    memory->deallocate(invsqrt_mass_p);
}

void AnharmonicCore::store_exponential_for_acceleration(const int nk_in[3],
                                                        int &nkrep_out,
                                                        std::complex<double> *exp_out,
                                                        std::complex<double> ***exp3_out)
{
    // For accelerating function V3 and V4 by avoiding continual call of std::exp.

    MPI_Bcast(&use_tuned_ver, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD);

    if (use_tuned_ver) {

        nk_grid[0] = nk_in[0];
        nk_grid[1] = nk_in[1];
        nk_grid[2] = nk_in[2];

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

        if (nk_grid[0] == nk_grid[1] && nk_grid[1] == nk_grid[2]) {
            nkrep_out = nk_grid[0];
            tune_type = 0;

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

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

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

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

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

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

        } else {
            tune_type = 1;
        }

        int ii, jj, kk;

        if (tune_type == 0) {

            double phase;

            memory->allocate(exp_phase, 2 * nkrep_out - 1);
#ifdef _OPENMP
#pragma omp parallel for private(phase)
#endif
            for (ii = 0; ii < 2 * nkrep_out - 1; ++ii) {
                phase = 2.0 * pi * static_cast<double>(ii - nkrep_out + 1)
                        / static_cast<double>(nkrep_out);
                exp_phase[ii] = std::exp(im * phase);
            }

        } else if (tune_type == 1) {

            double phase[3];

            memory->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] = 2.0 * pi * static_cast<double>(ii - nk_grid[0] + 1) / dnk[0];
                for (int jj = 0; jj < 2 * nk_grid[1] - 1; ++jj) {
                    phase[1] = 2.0 * pi * static_cast<double>(jj - nk_grid[1] + 1) / dnk[1];
                    for (int kk = 0; kk < 2 * nk_grid[2] - 1; ++kk) {
                        phase[2] = 2.0 * pi * static_cast<double>(kk - nk_grid[2] + 1) / dnk[2];
                        exp_phase3[ii][jj][kk] = std::exp(im * (phase[0] + phase[1] + phase[2]));
                    }
                }
            }
        }
    }
}

void AnharmonicCore::calc_self3omega_tetrahedron(const double Temp,
                                                 double **eval,
                                                 std::complex<double> ***evec,
                                                 const unsigned int ik_in,
                                                 const unsigned int snum,
                                                 const unsigned int nomega,
                                                 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 int nk = kpoint->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];

    int *kmap_identity, **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 = kpoint->kpoint_irred_all[ik_in][0].knum;
    const int knum_minus = kpoint->knum_minus[knum];
    double omega0 = eval[knum_minus][snum];

    kpoint->get_unique_triplet_k(ik_in,
                                 false,
                                 false,
                                 triplet);

    const auto npair_uniq = triplet.size();

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

    memory->allocate(kpairs, nk, 2);
    memory->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);
    }

    memory->allocate(v3_arr_loc, ns2);
    memory->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,
                                              dynamical->eval_phonon,
                                              dynamical->evec_phonon));
            }
        }
        MPI_Gather(&v3_arr_loc[0], ns2, MPI_DOUBLE,
                   v3_arr[ik * mympi->nprocs], ns2,
                   MPI_DOUBLE, 0, MPI_COMM_WORLD);
    }
    memory->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
        {
            memory->allocate(energy_tmp, 2, nk);
            memory->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
            {
                memory->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[k1][is] + eval[k2][js];
                    energy_tmp[1][ik] = eval[k1][is] - eval[k2][js];
                }
                for (iomega = 0; iomega < nomega; ++iomega) {
                    for (i = 0; i < 2; ++i) {
                        integration->calc_weight_tetrahedron(nk,
                                                             kmap_identity,
                                                             weight_tetra[i],
                                                             energy_tmp[i],
                                                             omega[iomega]);
                    }

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

                        omega_inner[0] = eval[k1][is];
                        omega_inner[1] = eval[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];
                }
            }
            memory->deallocate(energy_tmp);
            memory->deallocate(weight_tetra);
        }
#ifdef _OPENMP
#pragma omp parallel for
#endif
        for (iomega = 0; iomega < nomega; ++iomega) {
            ret[iomega] *= pi * std::pow(0.5, 4);
        }
        memory->deallocate(ret_private);
    }

    memory->deallocate(v3_arr);
    memory->deallocate(kmap_identity);
    memory->deallocate(kpairs);
}


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_COS_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_COS_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-trigonometric
    This function object returns the cosine of the input in radians.

    @par Header <boost/simd/function/cos.hpp>

    @par Notes
      The regular call to this functor is `cos(x)`,  but
    @c cos can also be called with two parameters as
      `cos(x, range_)` or with a decorator as `std_(cos)(x)` or `restricted_(cos)(x)`

    @c range_ is a tag that allows some control on the computation
      accuracy and speed.

      The control is on the reduction routine of the angle to the
      \f$[-\pi/4, \pi/4]\f$ interval.

      They actually are 3 reduction routines that respectly are
      sufficient for small_, medium_ and big_ angle values respecttively,
      to have (within
      cover test) one ulp of difference with the according crlibm
      (correctly rounded math library) result.

      Each tag covers respectively intervals \f$[-A, A]\f$ with :

        |             |   float  A       |  double  A     |
        |:-----------:|:----------------:|:--------------:|
        |    small_   |    \f$20\pi\f$   | \f$20\pi\f$    |
        |    medium_  |   \f$2^6\pi\f$   | \f$2^{18}\pi\f$|
        |     big_    |   \f$\infty\f$   | \f$\infty\f$   |

       In fact for each scalar singleton or simd vector of angles
       there are two possibilities :
       \arg one is to test if all vector element(s) are in the proper range
       for the consecutive increasing values of A until we reach a good
       one or the last :
       the corresponding template tags are @c small_, @c medium_ and @c big_
       \arg the second is to force directly a reduction method:
       the corresponding template tags are @c direct_small_, @c direct_medium_
       and @c direct_big_

       @par
       direct_small_ is NOT equivalent to small_ because there are also
       two other methods for \f$[0, \pi/4]\f$ (no reduction)
       and \f$[\pi/4, \pi/2]\f$
       (straight reduction) that are not considered in direct small_

       Note that for float the direct_big_ case is both early an hyper costly and
       shall be avoided whenever possible. To partially achieve this aim
       when double are available on the platform, this part of reduction
       is delegated to the double precision routines.

    @par Advices
       \arg If there is no restrictions ever on your angles and you care for precision
       use the default @c cos(x) or equivalently `cos(x, big_)`.
       \arg if you do not care for precision you can use
       `cos(x, medium_)` or `cos(x, small_)`
       that will be accurate for their proper range and degrade in accuracy
       with greater values.

       @par
       Now, the choice of direct or not relies on probabilities
       computations:
       assuming that a vector contains k elements and that testing all
       values that are in an interval takes c cycles and the probability of a value
       to be in interval \f$[a, b]\f$ is \f$p(a, b)\f$
       the number of cycles used by a
       direct\f${}_i\f$ method is simply the reduction time:
       \f$N(\f$direct\f${}_i)\f$
       On the other side the number of cycles for the non-direct methods will have a more
       complicated expression :

       \f$\hspace{5em}\sum_{i=1}^{m} p(A_{i-1}, A_i)^k N(\f$direct\f${}_i)\f$

       @par
       So the non direct methods will be interesting only if you want accurate
       results everywhere and have anyhow a big proportion of small angles.
       This is even more true (if possible) in simd and the more k is big, because
       of the kth power.

       @par
       For instance in the medium_ float case:

       \arg if angles are equidistributed  on \f$[0, 2^{16} \pi]\f$,
       the \f$p(0, 20\pi)\f$ will be
       less than \f$2^{-11}\f$ and thus (for example) if \f$k=4\f$,
       there will be 1 quadruple over 1.76e+13 falling in the small_ case...

       \arg Even sorting will do no good because the sort cost will be against
       the ratio of 1 successful quadruplet over 2048.
       \arg  Contrarily if your angles have a gaussian distribution with 0 mean and
       \f$10\pi\f$ standard deviation,  80% of the intervals will be in the "small_"
       case (95% of the values).
       \arg Finally for those that are sure of their angles taking place in a fixed
       range and want speed, three other template tags can be of choice as they use
       the chosen reduction, but return Nan for any outsider.

         `clipped_very_small_`, `clipped_small_` and `clipped_medium_`

    @par Decorators

       - std_ provides access to std::cos

       - restricted_ is equivalent to the clipped_very_small_ tag

    @see  sincos, cosd, cospi

    @par Example:

      @snippet cos.cpp cos

    @par Possible output:

      @snippet cos.txt cos

  **/
  IEEEValue cos(IEEEValue const& x);
} }
#endif

#include <boost/simd/function/scalar/cos.hpp>
#include <boost/simd/function/simd/cos.hpp>

#endif


//=======================================================================
// Copyright (c)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
/**
  * @file thorup_2kminus1.hpp
  * @brief
  * @author Jakub Ocwieja
  * @version 1.0
  * @date 2014-04-28
  */

#ifndef PAAL_THORUP_2KMINUS1_HPP
#define PAAL_THORUP_2KMINUS1_HPP

#include "paal/utils/functors.hpp"
#include "paal/utils/irange.hpp"
#include "paal/utils/assign_updates.hpp"

#include <boost/range/as_array.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/dijkstra_shortest_paths_no_color_map.hpp>

#include <queue>
#include <unordered_map>
#include <limits>
#include <random>
#include <cmath>

namespace paal {

/**
* @brief 2k-1 approximate distance oracle
*
* @tparam Graph graph
* @tparam EdgeWeightMap edge weight map
* @tparam VertexIndexMap vertex index map
* @tparam Rand random engine
*/
template <  typename Graph,
            typename VertexIndexMap,
            typename EdgeWeightMap,
            typename Rand=std::default_random_engine
            >
class distance_oracle_thorup2kminus1approximation {
    using DT = typename boost::property_traits<EdgeWeightMap>::value_type;
    using VT = typename boost::graph_traits<Graph>::vertex_descriptor;

    //! List of pairs (vertex index, distance to vertex)
    using DVect = std::vector< std::pair<int, DT> >;

    //! Maps vertex in a bunch into a distance to it
    using BunchMap = std::unordered_map<int, DT>;

    //! Index map stored to access internal structures
    VertexIndexMap m_index;

    //! For each vertex v a maximal layer number for which v belongs
    /** A_0 = V
    *   A_{i+1} \subset A_i
    *   A_k = \emptyset
    */
    std::vector< int > m_layer_num;

    //! For each vertex v a list of vertices of consecutive layers closest to v
    std::vector< DVect > m_parent;

    //! For each vertex v a set of vertices w closer to v than any vertex in layer m_layer_num[w]+1
    std::vector< BunchMap > m_bunch;

    /**
    * @brief Fills m_layer_num
    *
    * @param g Graph
    * @param k Approximation parameter = maximal number of layers
    * @param p Probability of being chosen to a next layer
    * @param random_engine Random engine
    *
    * @return Number of nonempty layers
    */
    int choose_layers(const Graph& g, int k, long double p, Rand & random_engine) {
        std::uniform_real_distribution<> dist(0,1);

        int max_layer_num = 0;
        long double logp = log(p);

        for (int ind: irange(num_vertices(g))) {
            m_layer_num[ind] = std::min(k-1, (int)(log(dist(random_engine)) / logp));
            assign_max(max_layer_num, m_layer_num[ind]);
        }

        return max_layer_num+1;
    }

    /**
     * @brief A visitor implementation for compute_parents Dijkstra's algorithm call
     *
     * @tparam NearestMap
     * @tparam Tag
     */
    template <typename NearestMap, typename Tag>
    class nearest_recorder : boost::base_visitor< nearest_recorder<NearestMap, Tag> > {

        //! Stores distances to the closest vertex in a particular layer
        NearestMap m_nearest_map;

    public:
        using event_filter = Tag;

        //! Constructor
        explicit nearest_recorder(NearestMap const nearest_map) : m_nearest_map(nearest_map) {}

        //! Copies nearest value from precdessor
        template <typename Edge>
        void operator()(Edge const e, Graph const &g) const {
            auto nearest = get(m_nearest_map, source(e,g));
            put(m_nearest_map, target(e,g), nearest);
        }

    };

    //! Constructs a visitor for compute_parents Dijkstra's algorithm call
    template <typename NearestMap, typename Tag>
    nearest_recorder<NearestMap, Tag>
    make_nearest_recorder(NearestMap nearest_map, Tag) {
        return nearest_recorder<NearestMap, Tag>{nearest_map};
    }

    /**
    * @brief Fills m_parent for a single layer
    *
    * @param g Graph
    * @param layer_num Number of layer
    */
    void compute_parents(const Graph& g, EdgeWeightMap edge_weight, int layer_num) {
        std::vector<DT> distance(num_vertices(g), std::numeric_limits<DT>::max());
        std::vector<int> nearest(num_vertices(g), -1);
        std::vector<VT> roots;

        for (auto v: boost::as_array(vertices(g))) {
            int v_ind = m_index[v];
            if (m_layer_num[v_ind] >= layer_num) {
                nearest[v_ind] = v_ind;
                distance[v_ind] = DT{};
                roots.push_back(v);
            }
        }

        boost::dijkstra_shortest_paths_no_init(
                g,
                roots.begin(),
                roots.end(),
                boost::dummy_property_map(),
                make_iterator_property_map(distance.begin(), m_index, distance[0]),
                edge_weight,
                m_index,
                utils::less{},
                boost::closed_plus<DT>(),
                DT{},
                boost::make_dijkstra_visitor(make_nearest_recorder(
                        make_iterator_property_map(nearest.begin(), m_index, nearest[0]),
                        boost::on_edge_relaxed{})
                    )
            );

        for (int ind: irange(num_vertices(g))) {
            m_parent[ind].push_back(std::make_pair(nearest[ind], distance[ind]));
        }
    }

     //! A distance type crafted to control logic of compute_cluster Dijkstra's algorithm call
    class cluster_dist {
        //! An actual distance
        DT m_value;
        //! Marks unmodified vertices
        bool m_unmodified;

        //! Private constructor
        cluster_dist(DT value, bool unmodified) :
            m_value(value), m_unmodified(unmodified) {}

    public:
        //! Public constructor
        cluster_dist(DT value = DT{}) :
            m_value(value), m_unmodified(false) {}

        //! Allows to create unmodified distances
        static cluster_dist
        make_limit(DT value = std::numeric_limits<DT>::max()) {
            return cluster_dist(value, true);
        }

        //! A comparator struct adjusted to recognize unmodified values
        /** Unmodified values are not smaller then any modified value. To recognize an umodified value we compare it
         *  with a maximal modified value of DT
         */
        struct less {
            //! A comparison operator
            bool operator()(cluster_dist a, cluster_dist b) const {
                return (a.m_value < b.m_value) && (b.m_unmodified || !a.m_unmodified);
            }
        };

        //! Plus operation struct
        struct plus {
            //! Sum operator
            cluster_dist operator()(cluster_dist a, cluster_dist b) const {
                return cluster_dist(a.m_value + b.m_value, a.m_unmodified || b.m_unmodified);
            }
        };

        //! An accessor to the distance
        const DT value() const {
            return m_value;
        }
    };

    /**
     * @brief A property_map with lazy initialization of distances
     *
     * For each vertex of a graph a distance is initialized with an upper limit on a value which causes edge
     * relaxation in compute_cluster Dijkstra's algorithm.
     */
    class cluster_distance_wrapper :
        public boost::put_get_helper<cluster_dist&, cluster_distance_wrapper>
    {
        //! A wrapped distance table
        std::vector< cluster_dist > *m_distance;

        //! An index map stored to access table structures
        VertexIndexMap m_index;

        //! A pointer to a parent table containing initial values of fields of m_distance
        /** The values stored here are copied into m_distance table when m_distance fields are accessed for the
         *  first time. For each vertex of a graph it contains an upper limit on a value which causes edge relaxation
         *  in compute_cluster Dijkstra's algorithm.
         */
        std::vector< DT > *m_limit;

        //! A table storing last access time to m_distance fields
        std::vector<int> *m_last_accessed;

        //! A value necessary to interpret m_last_accessed
        /** The value is initially different than any value in m_last_accessed.
         *  However, it is not necessarily bigger.
         */
        int m_now;

    public:
        typedef VT key_type;
        typedef cluster_dist value_type;
        typedef value_type& reference;
        typedef boost::lvalue_property_map_tag category;

        /**
         * @brief Constructor
         *
         * @param distance A helper vector - required to have num_vertices(g) fields
         * @param index An index map
         * @param limit Compute_cluster Dijkstra's algorithm relaxation limits
         * @param last_accessed A helper vector - required to have num_vertices(g) fields
         * @param now Required to be different than any last_accessed value
         */
        cluster_distance_wrapper(
                std::vector< cluster_dist > *distance,
                VertexIndexMap index,
                std::vector< DT > *limit,
                std::vector< int > *last_accessed,
                int now) :
            m_distance(distance),
            m_index(index),
            m_limit(limit),
            m_last_accessed(last_accessed),
            m_now(now) {}

        /**
         * @brief Map values accessor
         *
         * @param key Key
         *
         * @return Value
         */
        reference operator[](const key_type& key) const {
            int k_ind = m_index[key];
            if ((*m_last_accessed)[k_ind] != m_now) {
                (*m_last_accessed)[k_ind] = m_now;
                (*m_distance)[k_ind] = cluster_dist::make_limit((*m_limit)[k_ind]);
            }
            return (*m_distance)[k_ind];
        }
    };

    /**
     * @brief A visitor implementation for a compute_cluster Dijkstra's algorithm call
     *
     * @tparam DistanceMap
     * @tparam Tag
     */
    template <typename DistanceMap, typename Tag>
    class cluster_recorder : boost::base_visitor< cluster_recorder<DistanceMap, Tag> > {
        //! Vertex whose cluster is recorded
        int m_w_ind;

        //! A pointer to m_bunch field of the oracle
        std::vector< BunchMap >* m_bunch;

        //! Index map stored to access internal structures
        VertexIndexMap m_index;

        //! A distance map
        DistanceMap m_distance;

    public:
        using event_filter = Tag;

        explicit cluster_recorder(int w_ind, std::vector<BunchMap> *bunch,
                VertexIndexMap index, DistanceMap distance) :
            m_w_ind(w_ind), m_bunch(bunch), m_index(index), m_distance(distance) {}

        template <typename Vertex>
        void operator()(Vertex const v, Graph const &g) const {
            (*m_bunch)[m_index[v]].insert(std::make_pair(m_w_ind, m_distance[v].value()));
        }
    };

    /**
     * @brief
     *
     * @tparam DistanceMap
     * @tparam Tag
     * @param cluster
     * @param index
     * @param distance
     * @param Tag
     *
     * @return A visitor for a compute_cluster Dijkstra's algorithm call
     */
    template <typename DistanceMap, typename Tag>
    cluster_recorder<DistanceMap, Tag>
    make_cluster_recorder(int w_ind, std::vector<BunchMap> *bunch, VertexIndexMap index,
            DistanceMap distance, Tag) {
        return cluster_recorder<DistanceMap, Tag>{w_ind, bunch, index, distance};
    };

    /**
     * @brief Fills bunchs with vertices inside a cluster - a set of vertices
     *        which contains w in its bunch
     *
     * @param g Graph
     * @param edge_weight Edge weights
     * @param w Vertex
     * @param k Number of layers
     * @param limit Dijkstra's algorithm relaxation limits for each layer
     * @param distance A helper vector - required to have num_vertices(g) fields
     * @param last_accessed A helper vector - require to be initialized with negative values
     */
    void compute_cluster(const Graph& g, EdgeWeightMap edge_weight, VT w, int k,
            std::vector< std::vector<DT> > &limit,
            std::vector<cluster_dist> &distance, std::vector<int> &last_accessed) {
        DVect cluster;
        int w_ind = m_index[w];
        int w_layer_num = m_layer_num[w_ind];

        cluster_distance_wrapper distance_wrapper(
                &distance, m_index, &limit[w_layer_num + 1],
                &last_accessed, w_ind);
        distance_wrapper[w] = cluster_dist(DT{});

        boost::dijkstra_shortest_paths_no_color_map_no_init(
                g,
                w,
                boost::dummy_property_map(),
                distance_wrapper,
                edge_weight,
                m_index,
                typename cluster_dist::less(),
                typename cluster_dist::plus(),
                cluster_dist(std::numeric_limits<DT>::max()),
                cluster_dist(DT{}),
                boost::make_dijkstra_visitor(make_cluster_recorder(
                        w_ind, &m_bunch, m_index, distance_wrapper,
                        boost::on_examine_vertex{})
                    )
            );
    }

    /**
    * @brief Fills m_bunch
    *
    * @param g Graph
    * @param edge_weight Edge weight
    * @param k Number of layers
    */
    void compute_bunchs(const Graph& g, EdgeWeightMap edge_weight, int k) {
        //! Initialization of reusable structures
        std::vector< std::vector<DT> > limit(k+1,
                std::vector<DT>(num_vertices(g), std::numeric_limits<DT>::max()));
        for (int l: irange(k)) {
            for (int i: irange(num_vertices(g))) {
                limit[l][i] = m_parent[i][l].second;
            }
        }
        std::vector<cluster_dist> distance(num_vertices(g));
        std::vector<int> last_accessed(num_vertices(g), -1);

        for (auto v: boost::as_array(vertices(g))) {
            compute_cluster(g, edge_weight, v, k, limit, distance, last_accessed);
        }
    }

public:

    /**
    * @brief Constructor
    *
    * @param g graph
    * @param index vertex index map
    * @param edge_weight edge weight map
    * @param k approximation parameter
    * @param random_engine random engine
    */
    distance_oracle_thorup2kminus1approximation(const Graph &g,
            VertexIndexMap index,
            EdgeWeightMap edge_weight,
            int k,
            Rand && random_engine = Rand(5426u)) :
        m_index(index),
        m_layer_num(num_vertices(g)),
        m_parent(num_vertices(g)),
        m_bunch(num_vertices(g))
            {
        long double p = powl(num_vertices(g), -1./k);
        k = choose_layers(g, k, p, random_engine);
        for (int layer_num: irange(k)) {
            compute_parents(g, edge_weight, layer_num);
        }
        compute_bunchs(g, edge_weight, k);
    }

    //! Returns an 2k-1 approximate distance between two vertices in O(k) time
    /** Returns a distance of path going through one of parents of u or v */
    DT operator()(VT u, VT v) const {
        int u_ind = m_index[u], v_ind = m_index[v];
        typename std::unordered_map<int, DT>::const_iterator it;
        int l = 0;
        std::pair<int, DT> middle_vertex = m_parent[u_ind][l];
        while ((it = m_bunch[v_ind].find(middle_vertex.first)) == m_bunch[v_ind].end()) {
            ++l;
            middle_vertex = m_parent[v_ind][l];
            std::swap(u_ind, v_ind);
        }
        //! Returns d(v, middle) + d(middle, u)
        return it->second + middle_vertex.second;
    }
};

/**
* @brief
*
* @tparam Graph
* @tparam EdgeWeightMap
* @tparam VertexIndexMap
* @tparam Rand
* @param g - given graph
* @param k - approximation parameter
* @param index - graph index map
* @param edge_weight - graph edge weight map
* @param random_engine - random engine
*
* @return 2k-1 approximate distance oracle
*/
template <  typename Graph,
            typename EdgeWeightMap,
            typename VertexIndexMap,
            typename Rand=std::default_random_engine
         >
distance_oracle_thorup2kminus1approximation<Graph, VertexIndexMap, EdgeWeightMap, Rand>
make_distance_oracle_thorup2kminus1approximation(
        const Graph &g,
        const int k,
        VertexIndexMap index,
        EdgeWeightMap edge_weight,
        Rand && random_engine = Rand(5426u)) {
    return distance_oracle_thorup2kminus1approximation<Graph,
    VertexIndexMap,
    EdgeWeightMap,
    Rand>(g, index, edge_weight, k, std::move(random_engine));
}

/**
* @brief
*
* @tparam Graph
* @tparam P
* @tparam T
* @tparam R
* @tparam Rand
* @param g - given graph
* @param k - approximation parameter
* @param params - named parameters
* @param random_engine - random engine
*
* @return 2k-1 approximate distance oracle
*/
template <  typename Graph,
            typename P = char,
            typename T = boost::detail::unused_tag_type,
            typename R = boost::no_property,
            typename Rand=std::default_random_engine
         >
auto
make_distance_oracle_thorup2kminus1approximation(
        const Graph &g,
        const int k,
        const boost::bgl_named_params<P, T, R>& params = boost::no_named_parameters(),
        Rand && random_engine = Rand(5426u))
    -> distance_oracle_thorup2kminus1approximation<Graph,
    decltype(choose_const_pmap(get_param(params, boost::vertex_index), g, boost::vertex_index)),
    decltype(choose_const_pmap(get_param(params, boost::edge_weight), g, boost::edge_weight)),
    Rand> {
    return make_distance_oracle_thorup2kminus1approximation(g,
            k,
            choose_const_pmap(get_param(params, boost::vertex_index), g, boost::vertex_index),
            choose_const_pmap(get_param(params, boost::edge_weight), g, boost::edge_weight),
            std::move(random_engine));
}

} //paal

#endif // PAAL_THORUP_2KMINUS1_HPP


/*
 * 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 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2015 Blender Foundation.
 * All rights reserved.
 */

#ifndef __EIGEN3_EIGENVALUES_C_API_CC__
#define __EIGEN3_EIGENVALUES_C_API_CC__

/* Eigen gives annoying huge amount of warnings here, silence them! */
#if defined(__GNUC__) && !defined(__clang__)
#  pragma GCC diagnostic ignored "-Wlogical-op"
#endif

#include <Eigen/Core>
#include <Eigen/Eigenvalues>

#include "eigenvalues.h"

using Eigen::SelfAdjointEigenSolver;

using Eigen::Map;
using Eigen::MatrixXf;
using Eigen::VectorXf;

using Eigen::Success;

bool EIG_self_adjoint_eigen_solve(const int size,
                                  const float *matrix,
                                  float *r_eigen_values,
                                  float *r_eigen_vectors)
{
  SelfAdjointEigenSolver<MatrixXf> eigen_solver;

  /* Blender and Eigen matrices are both column-major. */
  eigen_solver.compute(Map<MatrixXf>((float *)matrix, size, size));

  if (eigen_solver.info() != Success) {
    return false;
  }

  if (r_eigen_values) {
    Map<VectorXf>(r_eigen_values, size) = eigen_solver.eigenvalues().transpose();
  }

  if (r_eigen_vectors) {
    Map<MatrixXf>(r_eigen_vectors, size, size) = eigen_solver.eigenvectors();
  }

  return true;
}

#endif /* __EIGEN3_EIGENVALUES_C_API_CC__ */


// This file is part of the dune-hdd project:
//   http://users.dune-project.org/projects/dune-hdd
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH
#define DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH

#include <memory>
#include <vector>

#include <boost/numeric/conversion/cast.hpp>

#include <dune/common/timer.hh>
#include <dune/common/static_assert.hh>

#include <dune/stuff/common/disable_warnings.hh>
# if HAVE_ALUGRID
#   include <dune/grid/alugrid.hh>
# endif
#include <dune/stuff/common/reenable_warnings.hh>

#if HAVE_DUNE_GRID_MULTISCALE
# include <dune/grid/multiscale/provider/interface.hh>
#endif

#include <dune/stuff/common/configuration.hh>
#include <dune/stuff/common/timedlogging.hh>
#include <dune/stuff/grid/layers.hh>
#include <dune/stuff/grid/provider.hh>
#include <dune/stuff/la/container.hh>
#include <dune/stuff/la/solver.hh>
#include <dune/stuff/grid/walker/functors.hh>

#include <dune/gdt/assembler/system.hh>
#include <dune/gdt/assembler/tmp-storage.hh>
#include <dune/gdt/discretefunction/default.hh>
#include <dune/gdt/functionals/l2.hh>
#include <dune/gdt/operators/oswaldinterpolation.hh>
#include <dune/gdt/operators/projections.hh>
#include <dune/gdt/playground/functionals/swipdg.hh>
#include <dune/gdt/playground/operators/elliptic-swipdg.hh>
#include <dune/gdt/playground/operators/fluxreconstruction.hh>
#include <dune/gdt/playground/products/swipdgpenalty.hh>
#include <dune/gdt/spaces/fv/default.hh>
#include <dune/gdt/spaces/rt/pdelab.hh>
#include <dune/gdt/spaces/dg.hh>

#include "base.hh"

namespace Dune {
namespace HDD {
namespace LinearElliptic {
namespace Discretizations {

// forward, for friendlyness
template< class GridImp, class RangeFieldImp, int rangeDim, int polynomialOrder, Stuff::LA::ChooseBackend la_backend >
class BlockSWIPDG;

// forward, needed in the Traits
template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder = 1,
#if HAVE_DUNE_FEM
          GDT::ChooseSpaceBackend space_backend = GDT::ChooseSpaceBackend::fem,
#else
# error No suitable space backend available!
#endif
          Stuff::LA::ChooseBackend la_backend  = Stuff::LA::default_sparse_backend >
class SWIPDG;

namespace internal {

template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder,
          GDT::ChooseSpaceBackend space_backend,
          Stuff::LA::ChooseBackend la_backend >
class SWIPDGTraits
  : public internal::ContainerBasedDefaultTraits< typename Stuff::LA::Container< RangeFieldImp, la_backend >::MatrixType,
                                                  typename Stuff::LA::Container< RangeFieldImp, la_backend >::VectorType>
{
public:
  typedef SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend > derived_type;
  typedef GridImp GridType;
  typedef RangeFieldImp RangeFieldType;
  static const unsigned int dimRange = rangeDim;
  static const unsigned int polOrder = polynomialOrder;

private:
  typedef GDT::Spaces::DGProvider< GridType, layer, space_backend, polOrder, RangeFieldType, dimRange > SpaceProvider;

  friend class SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend >;

public:
  typedef typename SpaceProvider::Type TestSpaceType;
  typedef TestSpaceType AnsatzSpaceType;
  typedef typename TestSpaceType::GridViewType GridViewType;
}; // class SWIPDGTraits

} // namespace internal

template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder,
          GDT::ChooseSpaceBackend space_backend,
          Stuff::LA::ChooseBackend la_backend >
class SWIPDG
  : public ContainerBasedDefault< internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder,
                                                          space_backend, la_backend > >
{
  typedef ContainerBasedDefault< internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder,
                                                         space_backend, la_backend > > BaseType;
  typedef SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend > ThisType;
public:
  typedef internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend >
      Traits;
  typedef GridImp GridType;
  using typename BaseType::ProblemType;
  using typename BaseType::BoundaryInfoType;
  using typename BaseType::TestSpaceType;
  using typename BaseType::AnsatzSpaceType;
  using typename BaseType::MatrixType;
  using typename BaseType::VectorType;
  using typename BaseType::GridViewType;
  using typename BaseType::RangeFieldType;

  typedef typename TestSpaceType::PatternType PatternType;

  static const unsigned int dimDomain = BaseType::dimDomain;
  static const unsigned int dimRange = BaseType::dimRange;

private:
  typedef typename Traits::SpaceProvider SpaceProvider;

  typedef Stuff::Grid::ProviderInterface< GridType >      GridProviderType;
#if HAVE_DUNE_GRID_MULTISCALE
  typedef grid::Multiscale::ProviderInterface< GridType > MsGridProviderType;
#endif
  using typename BaseType::AffinelyDecomposedMatrixType;
  using typename BaseType::AffinelyDecomposedVectorType;

  typedef typename ProblemType::DiffusionFactorType::NonparametricType DiffusionFactorType;
  typedef typename ProblemType::DiffusionTensorType::NonparametricType DiffusionTensorType;
  typedef GDT::Operators::EllipticSWIPDG< DiffusionFactorType, MatrixType
                                        , TestSpaceType, AnsatzSpaceType
                                        , GridViewType, DiffusionTensorType > EllipticOperatorType;

public:
  static std::string static_id();

  template< Stuff::Grid::ChooseLayer lr = layer >
  SWIPDG(GridProviderType& grid_provider,
         const Stuff::Common::Configuration& bound_inf_cfg,
         const ProblemType& prob,
         const int level_or_subdomain = 0,
         const std::vector< std::string >& only_these_products = {}
#if HAVE_DUNE_GRID_MULTISCALE
       , typename std::enable_if<    (lr != Stuff::Grid::ChooseLayer::local)
                                  && (lr != Stuff::Grid::ChooseLayer::local_oversampled), void >::type* /*disable_for_local_grid_parts*/ = nullptr
#endif
        );

#if HAVE_DUNE_GRID_MULTISCALE

  SWIPDG(const MsGridProviderType& grid_provider,
         const Stuff::Common::Configuration& bound_inf_cfg,
         const ProblemType& prob,
         const int level_or_subdomain = 0,
         const std::vector< std::string >& only_these_products = {});

#endif // HAVE_DUNE_GRID_MULTISCALE

  void init(const bool prune = false);

private:
  friend class BlockSWIPDG< GridImp, RangeFieldImp, rangeDim, polynomialOrder, la_backend >;

  const RangeFieldType beta_;
  using BaseType::pattern_;
  const std::vector< std::string > only_these_products_;
}; // class SWIPDG

#if HAVE_ALUGRID && HAVE_DUNE_FEM
# if HAVE_DUNE_ISTL

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::leaf,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                        Stuff::Grid::ChooseLayer::leaf,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::istl_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::leaf >(GridProviderType&,
                                               const Stuff::Common::Configuration&,
                                               const ProblemType&,
                                               const int level_or_subdomain,
                                               const std::vector< std::string >&,
                                               void*);

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::level,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                        Stuff::Grid::ChooseLayer::level,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::istl_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::level >(GridProviderType&,
                                                const Stuff::Common::Configuration&,
                                                const ProblemType&,
                                                const int level_or_subdomain,
                                                const std::vector< std::string >&,
                                                void*);

#   if HAVE_MPI

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                              Stuff::Grid::ChooseLayer::leaf,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                        Stuff::Grid::ChooseLayer::leaf,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::istl_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::leaf >(GridProviderType&,
                                               const Stuff::Common::Configuration&,
                                               const ProblemType&,
                                               const int level_or_subdomain,
                                               const std::vector< std::string >&,
                                               void*);

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                              Stuff::Grid::ChooseLayer::level,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                        Stuff::Grid::ChooseLayer::level,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::istl_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::level >(GridProviderType&,
                                                const Stuff::Common::Configuration&,
                                                const ProblemType&,
                                                const int level_or_subdomain,
                                                const std::vector< std::string >&,
                                                void*);

#   endif // HAVE_MPI
#   if HAVE_DUNE_GRID_MULTISCALE

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::local,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::local_oversampled,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;

#     if HAVE_MPI

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm >,
                              Stuff::Grid::ChooseLayer::local,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm >,
                              Stuff::Grid::ChooseLayer::local_oversampled,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::istl_sparse >;

#     endif // HAVE_MPI
#   endif // HAVE_DUNE_GRID_MULTISCALE
# endif // HAVE_DUNE_ISTL
# if HAVE_EIGEN

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::leaf,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                        Stuff::Grid::ChooseLayer::leaf,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::eigen_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::leaf >(GridProviderType&,
                                               const Stuff::Common::Configuration&,
                                               const ProblemType&,
                                               const int level_or_subdomain,
                                               const std::vector< std::string >&,
                                               void*);

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::level,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                        Stuff::Grid::ChooseLayer::level,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::eigen_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::level >(GridProviderType&,
                                                const Stuff::Common::Configuration&,
                                                const ProblemType&,
                                                const int level_or_subdomain,
                                                const std::vector< std::string >&,
                                                void*);

#   if HAVE_MPI

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                              Stuff::Grid::ChooseLayer::leaf,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                        Stuff::Grid::ChooseLayer::leaf,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::eigen_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::leaf >(GridProviderType&,
                                               const Stuff::Common::Configuration&,
                                               const ProblemType&,
                                               const int level_or_subdomain,
                                               const std::vector< std::string >&,
                                               void*);

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                              Stuff::Grid::ChooseLayer::level,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;
extern template SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm  >,
                        Stuff::Grid::ChooseLayer::level,
                        double,
                        1,
                        1,
                        GDT::ChooseSpaceBackend::fem,
                        Stuff::LA::ChooseBackend::eigen_sparse >
    ::SWIPDG< Stuff::Grid::ChooseLayer::level >(GridProviderType&,
                                                const Stuff::Common::Configuration&,
                                                const ProblemType&,
                                                const int level_or_subdomain,
                                                const std::vector< std::string >&,
                                                void*);

#   endif // HAVE_MPI
#   if HAVE_DUNE_GRID_MULTISCALE

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::local,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, No_Comm >,
                              Stuff::Grid::ChooseLayer::local_oversampled,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;

#     if HAVE_MPI

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm >,
                              Stuff::Grid::ChooseLayer::local,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;

extern template class SWIPDG< ALUGrid< 2, 2, simplex, conforming, MPI_Comm >,
                              Stuff::Grid::ChooseLayer::local_oversampled,
                              double,
                              1,
                              1,
                              GDT::ChooseSpaceBackend::fem,
                              Stuff::LA::ChooseBackend::eigen_sparse >;

#     endif // HAVE_MPI
#   endif // HAVE_DUNE_GRID_MULTISCALE
# endif // HAVE_EIGEN
#endif // HAVE_ALUGRID && HAVE_DUNE_FEM

} // namespace Discretizations
} // namespace LinearElliptic
} // namespace HDD
} // namespace Dune

#endif // DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH