// FaceMorphDlg.cpp : implementation file
//

#include "pch.h"
#include "framework.h"
#include "FaceMorph.h"
#include "FaceMorphDlg.h"
#include "FileHandle.h"
#include "DxDlg.h"
#include "afxdialogex.h"
#include <Eigen/Core>
#include <Eigen/SVD>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif
const int LINE_BUFF_SIZE = 4096;
const float g_fFOV = 12.5936f;// used to be 45.5f;
const float g_fAspect = 1.0f;
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
};
//---------------------------------------------------------------//
CAboutDlg::CAboutDlg() : CDialog(IDD_ABOUTBOX)
{
}
//---------------------------------------------------------------//
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}
//---------------------------------------------------------------//
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()

// CFaceMorphDlg dialog

//---------------------------------------------------------------//
CFaceMorphDlg::CFaceMorphDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_FACEMORPH_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	//cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, alpha, A, k, B, n, beta, C, n);
}
//---------------------------------------------------------------//
CFaceMorphDlg::~CFaceMorphDlg()
{

}
//---------------------------------------------------------------//
void CFaceMorphDlg::DoDataExchange(CDataExchange* pDX)
{
	for (int h = 0; h < NUM_SLIDERS; h++)
	{
		DDX_Control(pDX, IDC_SLIDER1 + h, m_Slider[h]);
	}
	CDialog::DoDataExchange(pDX);
}
//---------------------------------------------------------------//
BEGIN_MESSAGE_MAP(CFaceMorphDlg, CDialog)
	ON_WM_TIMER()
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_HSCROLL()
	ON_WM_QUERYDRAGICON()
	ON_WM_CLOSE()
	ON_BN_CLICKED(IDC_RESET, &CFaceMorphDlg::OnBnClickedReset)
	ON_BN_CLICKED(IDC_CHECK1, &CFaceMorphDlg::OnBnClickedCheck1)
	ON_BN_CLICKED(IDC_EXPORT, &CFaceMorphDlg::OnBnClickedExport)
END_MESSAGE_MAP()
//---------------------------------------------------------------//
// CFaceMorphDlg message handlers
BOOL CFaceMorphDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	SetDlgItemText(IDC_EXPORT_PATH, L"C:\\_3DMMExport");

	CRect rtFrame;
	GetDlgItem(IDC_FRAME)->GetWindowRect(rtFrame);
	ScreenToClient(rtFrame);

	for (int h = 0; h < NUM_SLIDERS; h++)
	{
		m_Slider[h].SetRange(0, 2 * SLIDER_CENTER);
		m_Slider[h].SetPos(SLIDER_CENTER);
	}
	m_pRenderWnd = new CDxWnd();

	BOOL b2 = m_pRenderWnd->Create(NULL, _T("DxWnd"), WS_VISIBLE, rtFrame, this, 1);

	//calculate the eigenvalues here and pass them to CDxDlg
	//first we need to find the Meshess folder//
	//This will be at a higher level. Recursively search until we find it
	CString strMeshPath;
	TCHAR szFilePath[MAX_PATH + _ATL_QUOTES_SPACE];
	DWORD dwFLen = ::GetModuleFileName(NULL, szFilePath + 0, MAX_PATH);
	strMeshPath = CString(szFilePath);

	long nRight = strMeshPath.ReverseFind(_T('\\'));//remove exe name
	strMeshPath = strMeshPath.Left(nRight);//this will ne the same folder as the EXe
	bool bFoundMeshFolder = false;
	int nNumAttempts = 0;
	while (!bFoundMeshFolder && nNumAttempts < 3)
	{
		nRight = strMeshPath.ReverseFind(_T('\\'));//move one folder up
		strMeshPath = strMeshPath.Left(nRight);//this will ne the same folder as
		nNumAttempts++;
		if (DoesDirExist(strMeshPath + L"\\Meshes")) {
			bFoundMeshFolder = true;
			strMeshPath.Append(L"\\Meshes\\");
		}

	}
	if (bFoundMeshFolder)
	{
		CalcEigenValues(strMeshPath);
	}
	else
	{
		AfxMessageBox(L"Failed to find Mesh Folder");
	}

	// Draw the background gradient.
	m_pRenderWnd->Initialize(this);
	m_pRenderWnd->MoveWindow(rtFrame);
	SetTimer(1, 50, NULL);

	/*dgesvd("All", "All", &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work, &lwork,
		&info);*/

	return TRUE;  // return TRUE  unless you set the focus to a control
}
//---------------------------------------------------------------//
bool CFaceMorphDlg::CalcEigenValues(CString strMeshPath)
{
	if (!m_pRenderWnd)
	{
		AfxMessageBox(L"m_pRenderWnd not created");
		return false;
	}
	std::vector<std::vector<float> > vMeshes;

	int nNumVertices = -1;
	int nI1 = 0;
	int nI2 = 0;
	int nI3 = 0;

	////////////////////////////////////////////////////////////////////////////////////////
	//STEP ONE: load the files
	////////////////////////////////////////////////////////////////////////////////////////
	CFileFind finder;
	//count the number of files we will have to load
	int nNumFiles = 0;
	BOOL bWorking = finder.FindFile(strMeshPath + _T("*.mesh"));
	while (bWorking)
	{
		bWorking = finder.FindNextFile();
		nNumFiles++;
	}
	finder.Close();

	int nFileCounter = 0;
	bWorking = finder.FindFile(strMeshPath + _T("*.mesh"));
	while (bWorking)
	{
		bWorking = finder.FindNextFile();
		if (!bWorking) {
			long ert = 0;
		}
		CString strPath = finder.GetFilePath();
		int nCounter = 0;
		CFileHandle file(strPath, TEXT("r"));
		if (file.GetFile() != 0)
		{
			//cound the vertices in advance and make sure they fit the vector
			while (!feof(file.GetFile()))
			{
				CHAR buffer[LINE_BUFF_SIZE];
				fgets(buffer, LINE_BUFF_SIZE, file.GetFile());
				if (buffer[0] == 'v')
				{
					nCounter++;
				}
			}
			if (nNumVertices == -1)
			{
				nNumVertices = nCounter;
			}
			else if (nNumVertices != nCounter)
			{
				finder.Close();
				AfxMessageBox(L"wrong size");
				return false;
			}
			if (nFileCounter == 0)
			{
				m_pRenderWnd->m_nNumVertices = nNumVertices;
				//we have enough info now to preallocate memory to avoid performance penalties caused by dynamic memory allocations...
				vMeshes.reserve(nNumFiles);
				vMeshes.resize(nNumFiles);
				int nNumVerticesXYZ = nNumVertices * 3;
				for (int h = 0; h < nNumFiles; h++)
				{
					vMeshes[h].reserve(nNumVerticesXYZ);
					vMeshes[h].resize(nNumVerticesXYZ);
				}

				m_pRenderWnd->m_AveFace.reserve(nNumVerticesXYZ);
				m_pRenderWnd->m_AveFace.resize(nNumVerticesXYZ);
				m_pRenderWnd->m_Mesh.reserve(nNumVerticesXYZ);
				m_pRenderWnd->m_Mesh.resize(nNumVerticesXYZ);
			}
			rewind(file.GetFile());
			int nIndex = 0;
			float x = 0;
			float y = 0;
			float z = 0;
			while (!feof(file.GetFile()))
			{
				CHAR buffer[LINE_BUFF_SIZE];
				fgets(buffer, LINE_BUFF_SIZE, file.GetFile());
				if (0 == strncmp("v ", buffer, 2))
				{
					//load vertices into the vectors
					sscanf_s(buffer + 1, "%f %f %f", &x, &y, &z);
					m_pRenderWnd->m_AveFace[nIndex] += x;
					m_pRenderWnd->m_AveFace[nIndex + nNumVertices] += y;
					m_pRenderWnd->m_AveFace[nIndex + 2 * nNumVertices] += z;
					vMeshes[nFileCounter][nIndex] = x;
					vMeshes[nFileCounter][nIndex + nNumVertices] = y;
					vMeshes[nFileCounter][nIndex + 2 * nNumVertices] = z;
					nIndex++;
				}
				else if (nFileCounter == 0 && 0 == strncmp("f ", buffer, 2))
				{
					//load face information (list of three points forming a triangle). Do it for the first mesh only as the rest should be the same
					sscanf_s(buffer + 1, "%d %d %d", &nI1, &nI2, &nI3);
					m_pRenderWnd->m_vIndices.push_back(nI1 - 1);
					m_pRenderWnd->m_vIndices.push_back(nI2 - 1);
					m_pRenderWnd->m_vIndices.push_back(nI3 - 1);
				}
			}
			nFileCounter++;
		}
	}
	//finder.Close(); //note, CFileFinder calls Close on destructor, so we don't need to call this here

	////////////////////////////////////////////////////////////////////////////////////////
	//STEP TWO: calculate the mean/average face mesh
	////////////////////////////////////////////////////////////////////////////////////////
	for_each(m_pRenderWnd->m_AveFace.begin(), m_pRenderWnd->m_AveFace.end(), [nNumFiles](float& x) {x = x / nNumFiles; });

	////////////////////////////////////////////////////////////////////////////////////////
	//STEP THREE: subtract the mean from each face mesh
	////////////////////////////////////////////////////////////////////////////////////////
	auto t = m_pRenderWnd->m_AveFace;
	for (auto& m : vMeshes)
	{
		int j = 0;
		for_each(m.begin(), m.end(), [&j, &t](float& x) {x = x - t[j]; j++; });
	}
	/*for (int h = 0; h < nNumFiles; h++)//older style for loop - but is it easier to read??
	{
		for (int j = 0; j < nNumVertices * 3; j++){
			vMeshes[h][j] = vMeshes[h][j] - m_pRenderWnd->m_AveFace[j];
		}
	}*/
	////////////////////////////////////////////////////////////////////////////////////////
	//STEP FOUR: calculate the eigenvectors
	////////////////////////////////////////////////////////////////////////////////////////
	int nNumVerticesXYZ = nNumVertices * 3;
	Eigen::MatrixXf X(nNumVerticesXYZ, nNumFiles);
	int h = 0;
	for (auto& m : vMeshes)
	{
		int j = 0;
		for_each(m.begin(), m.end(), [&j, h, &X](float& x) {X(j, h) = x; j++; });
		h++;
	}
	/*for (int h = 0; h < nNumFiles; h++)//older style for loop - is this easier to read??
	{
		for (int j = 0; j < nNumVerticesXYZ; j++) {
			X(j, h) = vMeshes[h][j];
		}
	}*/

	//Use Principle Component Analysis. SVD routine in the Eigen library to do this
	Eigen::JacobiSVD<Eigen::MatrixXf> svd(X, Eigen::ComputeThinU | Eigen::ComputeThinV);
	Eigen::MatrixXf U = svd.matrixU();
	Eigen::MatrixXf V = svd.matrixV();
	Eigen::VectorXf s = svd.singularValues();

	if (s.rows() < NUM_EIGENS)
	{
		CString strMsg;
		strMsg.Format(L"Not enough eigenvectors found. We need at least %d. Please increase the number of meshes and restart the application.", NUM_EIGENS);
		AfxMessageBox(strMsg);
		return false;
	}

	//There will be nNumFiles eigenvectors, but only take the first 20 (NUM_EIGENS)
	m_pRenderWnd->m_Eigen.reserve(NUM_EIGENS);
	m_pRenderWnd->m_Eigen.resize(NUM_EIGENS);
	for (int h = 0; h < NUM_EIGENS; h++)
	{
		m_pRenderWnd->m_Eigen[h].reserve(nNumVerticesXYZ);
		m_pRenderWnd->m_Eigen[h].resize(nNumVerticesXYZ);
		int j = 0;
		for_each(m_pRenderWnd->m_Eigen[h].begin(), m_pRenderWnd->m_Eigen[h].end(), [&j, h, &U](float& x) {x = U(j, h); j++; });
		/*for (int j = 0; j < nNumVerticesXYZ; j++) 	{//older style, again, isn't it easier to read?
			m_pRenderWnd->m_Eigen[h][j] = U(j, h);
		}*/
	}
	//We now have the eigenvectors. The mesh that is rendered will be the average/mean mesh plus a linear combination of the
	//the eignevector meshes (m_Eigen). That is each eigenvector is multipled by the scalar corresponding to each slider in the UI.
	return true;
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnTimer(UINT_PTR nIdEvent)
{
	if (nIdEvent == 1)
	{
		if(m_pRenderWnd) m_pRenderWnd->Render();
	}
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}
//---------------------------------------------------------------//
// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.
void CFaceMorphDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}

	for (int h = 0; h < 20; h++)
	{
		m_Slider[h].SetRange(0, 2 * SLIDER_CENTER);
		m_Slider[h].SetPos(SLIDER_CENTER);
	}
}
//---------------------------------------------------------------//
// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CFaceMorphDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnSlider()
{
	if (!m_pRenderWnd) return;
	for (int h = 0; h < NUM_SLIDERS; h++)
	{
		m_Scalars.m_Next[h] = (float)m_Slider[h].GetPos() - SLIDER_CENTER;
	}
	m_pRenderWnd->RecalcMesh(m_Scalars);
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	CSliderCtrl* pSlider = reinterpret_cast<CSliderCtrl*>(pScrollBar);
	for (int h = 0; h < NUM_SLIDERS; h++)
	{
		if (pSlider == &m_Slider[h])
		{
			OnSlider();
		}
	}
	CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnClose()
{
	if (m_pRenderWnd)
	{
		m_pRenderWnd->ShutDown();
		m_pRenderWnd->DestroyWindow();
		delete m_pRenderWnd;
		m_pRenderWnd = 0;
	}

	CDialog::OnClose();
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnBnClickedReset()
{
	for (int h = 0; h < NUM_SLIDERS; h++)
	{
		m_Slider[h].SetPos(SLIDER_CENTER);
	};
	OnSlider();
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnBnClickedCheck1()
{
	if (!m_pRenderWnd) return;
	m_pRenderWnd->m_bWireframe = !m_pRenderWnd->m_bWireframe;
}
//-------------------------------------------------------------------------------------//
bool CFaceMorphDlg::DoesDirExist(CString str)
{
	DWORD dwRetVal = GetFileAttributes(str);
	if (dwRetVal == 0xFFFFFFFF)
	{
		return false;
	}
	else if (dwRetVal & FILE_ATTRIBUTE_DIRECTORY)
	{
		return true;
	}
	return false;
}
//---------------------------------------------------------------//
void CFaceMorphDlg::OnBnClickedExport()
{
	CString strLine;
	CString strExportPath;
	CWaitCursor wait;

	GetDlgItemText(IDC_EXPORT_PATH, strExportPath);
	if (!DoesDirExist(strExportPath))
	{
		AfxMessageBox(L"Folder doesn't exist!");
		return;
	}
	//export eigenvalues
	CStdioFile f;
	int nNumVerticesXYZ = m_pRenderWnd->m_nNumVertices * 3;
	if (f.Open(strExportPath + L"\\Eigen.dat", CFile::modeCreate | CFile::modeWrite))
	{
		for (int j = 0; j < nNumVerticesXYZ; j++)
		{
			for (int h = 0; h < NUM_EIGENS; h++)
			{
				strLine.Format(L"%f", m_pRenderWnd->m_Eigen[h][j]);
				f.WriteString(strLine);
			}
			f.WriteString(L"\n");
		}
		f.Close();
	}
	else
	{
		AfxMessageBox(L"Could not save EigenVector file");
		return;
	}

	if (f.Open(strExportPath + L"\\MeanFace.dat", CFile::modeCreate | CFile::modeWrite))
	{
		for (int j = 0; j < nNumVerticesXYZ; j++)
		{
			strLine.Format(L"%f\n", m_pRenderWnd->m_AveFace[j]);
			f.WriteString(strLine);
		}
		f.Close();
	}
	else
	{
		AfxMessageBox(L"Could not save file");
		return;
	}

	if (f.Open(strExportPath + L"\\MeanFace.obj", CFile::modeCreate | CFile::modeWrite))
	{
		for (int j = 0; j < m_pRenderWnd->m_nNumVertices; j++)
		{
			strLine.Format(L"v %f %f %f\n",
				m_pRenderWnd->m_AveFace[j],
				m_pRenderWnd->m_AveFace[m_pRenderWnd->m_nNumVertices + j],
				m_pRenderWnd->m_AveFace[2 * m_pRenderWnd->m_nNumVertices + j]);
			f.WriteString(strLine);
		}
		size_t nNumTriangles = m_pRenderWnd->m_vIndices.size() / 3;
		for (int j = 0; j < nNumTriangles; j++)
		{
			strLine.Format(L"f %d %d %d\n",
				m_pRenderWnd->m_vIndices[3 * j + 0] + 1,
				m_pRenderWnd->m_vIndices[3 * j + 1] + 1,
				m_pRenderWnd->m_vIndices[3 * j + 2] + 1);
			f.WriteString(strLine);
		}
		f.Close();
	}
	else
	{
		AfxMessageBox(L"Could not save file");
		return;
	}
	//To load this into matlab using a .,m file, something along the following should work
	//fid = fopen('C:\_3DMMExport\MeanFace.dat');
	//avgFace = [];
	//while ~feof(fid)
	//	tline = fgetl(fid);
	//avgFace = [avgFace; sscanf(tline(1:end), '%f')'];
	//	end
	//	fclose(fid);
	//fid = fopen('C:\_3DMMExport\Eigen.dat');
	//egn = [length(avgFace), 20];
	//k = 1;
	//while ~feof(fid)
	//	tline = fgetl(fid);
	//egn(k, 1:20) = sscanf(tline(1:end), '%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f');
	//k = k + 1;
	//end
	//	fclose(fid);
}


// Copyright 2005 Mitsubishi Electric Research Laboratories All Rights
// Reserved.

// Permission to use, copy and modify this software and its documentation
// without fee for educational, research and non-profit purposes, is hereby
// granted, provided that the above copyright notice and the following three
// paragraphs appear in all copies.

// To request permission to incorporate this software into commercial products
// contact: Vice President of Marketing and Business Development; Mitsubishi
// Electric Research Laboratories (MERL), 201 Broadway, Cambridge, MA 02139 or
// <license@merl.com>.

// IN NO EVENT SHALL MERL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
// INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF
// THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF MERL HAS BEEN
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

// MERL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND MERL
// HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR
// MODIFICATIONS.

#include <cstdio>
#include <cstdlib>
#include <cmath>

// Include Eigen
#include <Eigen/Core>

#define BRDF_SAMPLING_RES_THETA_H       90
#define BRDF_SAMPLING_RES_THETA_D       90
#define BRDF_SAMPLING_RES_PHI_D         360

#define RED_SCALE (1.0/1500.0)
#define GREEN_SCALE (1.15/1500.0)
#define BLUE_SCALE (1.66/1500.0)

struct MerlBRDF {

   /* Storage of the BRDF data */
   double* brdf;

   /* Constructor and destructor */
   MerlBRDF() : brdf(nullptr) {
   }

   ~MerlBRDF() {
      if(brdf != nullptr) {
         delete[] brdf;
      }
   }

   /* Evaluate the BRDF for a given couple of vectors */
   template<class RGB, class Vector>
   RGB const value(const Vector& wi, const Vector& wo) const {
      double ti = acos(wi.z);
      double pi = atan2(wi.y, wi.x);
      double to = acos(wo.z);
      double po = atan2(wo.y, wo.x);

      double r, g, b;
      lookup_brdf_val(ti, pi, to, po, r, g, b);
      return RGB(r, g, b);
   }

   /* Read BRDF data */
   bool read_brdf(const std::string& filename)
   {
      FILE *f = fopen(filename.c_str(), "rb");
      if (!f) {
         return false;
      }

      int dims[3];
      fread(dims, sizeof(int), 3, f);
      int n = dims[0] * dims[1] * dims[2];
      if (n != BRDF_SAMPLING_RES_THETA_H *
            BRDF_SAMPLING_RES_THETA_D *
            BRDF_SAMPLING_RES_PHI_D / 2)
      {
         fprintf(stderr, "Dimensions don't match\n");
         fclose(f);
         return false;
      }

      brdf = (double*) malloc (sizeof(double)*3*n);
      fread(brdf, sizeof(double), 3*n, f);

      fclose(f);
      return true;
   }

   /* Project this BRDF to SH
    *
    * Return a std::vector containing an Eigen::MatrixXf for each input
    * elevation. This matrix size is SH::Terms(order) x RGB.
    */
   template<class RGB, class Vector, class SH>
   std::vector<Eigen::MatrixXf> projectToSH(int nbElev, int order, int M=10000) const {

      // Random sampler
      std::mt19937 gen(0);
      std::uniform_real_distribution<float> dist(0.0,1.0);

      // Return vector
      std::vector<Eigen::MatrixXf> res;
      res.reserve(nbElev);

      // Sample each incident direction
      for(int e=0; e<nbElev; ++e) {

         // Get the current input vector
         const float th = 0.5*M_PI * e / float(nbElev);
         const Vector wi(sin(th), 0.0, cos(th));

         Eigen::MatrixXf shCoeffs(SH::Terms(order), 3);
         for(int i=0; i<M; ++i) {
            // Sample the cosine of the elevation
            Vector wo;
            wo.z = 2.0*dist(gen) - 1.0;
            const float z2 = wo.z*wo.z;
            if(wo.z < 0.0) { continue; }

            // Sample the azimuth
            const float phi = 2.0*M_PI*dist(gen);
            wo.x = sqrt(1.0f-z2) * cos(phi);
            wo.y = sqrt(1.0f-z2) * sin(phi);

            // Evaluate the function and the basis vector
            const auto rgb = value<RGB, Vector>(wi, wo);
            const auto ylm = SH::FastBasis(wo, order);
            shCoeffs.col(0) += rgb[0]*ylm;
            shCoeffs.col(1) += rgb[1]*ylm;
            shCoeffs.col(2) += rgb[2]*ylm;
         }
         shCoeffs *= 4.0*M_PI / float(M);
         res.push_back(shCoeffs);
      }

      return res;
   }

   // cross product of two vectors
   void cross_product (double* v1, double* v2, double* out) const
   {
      out[0] = v1[1]*v2[2] - v1[2]*v2[1];
      out[1] = v1[2]*v2[0] - v1[0]*v2[2];
      out[2] = v1[0]*v2[1] - v1[1]*v2[0];
   }

   // normalize vector
   void normalize(double* v) const
   {
      // normalize
      double len = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
      v[0] = v[0] / len;
      v[1] = v[1] / len;
      v[2] = v[2] / len;
   }

   // rotate vector along one axis
   void rotate_vector(double* vector, double* axis, double angle, double* out) const
   {
      double temp;
      double cross[3];
      double cos_ang = cos(angle);
      double sin_ang = sin(angle);

      out[0] = vector[0] * cos_ang;
      out[1] = vector[1] * cos_ang;
      out[2] = vector[2] * cos_ang;

      temp = axis[0]*vector[0]+axis[1]*vector[1]+axis[2]*vector[2];
      temp = temp*(1.0-cos_ang);

      out[0] += axis[0] * temp;
      out[1] += axis[1] * temp;
      out[2] += axis[2] * temp;

      cross_product (axis,vector,cross);

      out[0] += cross[0] * sin_ang;
      out[1] += cross[1] * sin_ang;
      out[2] += cross[2] * sin_ang;
   }

   // convert standard coordinates to half vector/difference vector coordinates
   void std_coords_to_half_diff_coords(double theta_in, double fi_in,
                                       double theta_out, double fi_out,
                                       double& theta_half,double& fi_half,
                                       double& theta_diff,double& fi_diff ) const
   {

      // compute in vector
      double in_vec_z = cos(theta_in);
      double proj_in_vec = sin(theta_in);
      double in_vec_x = proj_in_vec*cos(fi_in);
      double in_vec_y = proj_in_vec*sin(fi_in);
      double in[3]= {in_vec_x,in_vec_y,in_vec_z};
      normalize(in);

      // compute out vector
      double out_vec_z = cos(theta_out);
      double proj_out_vec = sin(theta_out);
      double out_vec_x = proj_out_vec*cos(fi_out);
      double out_vec_y = proj_out_vec*sin(fi_out);
      double out[3]= {out_vec_x,out_vec_y,out_vec_z};
      normalize(out);

      // compute halfway vector
      double half_x = (in_vec_x + out_vec_x)/2.0f;
      double half_y = (in_vec_y + out_vec_y)/2.0f;
      double half_z = (in_vec_z + out_vec_z)/2.0f;
      double half[3] = {half_x,half_y,half_z};
      normalize(half);

      // compute  theta_half, fi_half
      theta_half = acos(half[2]);
      fi_half = atan2(half[1], half[0]);

      double bi_normal[3] = {0.0, 1.0, 0.0};
      double normal[3] = { 0.0, 0.0, 1.0 };
      double temp[3];
      double diff[3];

      // compute diff vector
      rotate_vector(in, normal , -fi_half, temp);
      rotate_vector(temp, bi_normal, -theta_half, diff);

      // compute  theta_diff, fi_diff
      theta_diff = acos(diff[2]);
      fi_diff = atan2(diff[1], diff[0]);

   }

   // Lookup theta_half index
   // This is a non-linear mapping!
   // In:  [0 .. pi/2]
   // Out: [0 .. 89]
   inline int theta_half_index(double theta_half) const
   {
      if (theta_half <= 0.0)
         return 0;
      double theta_half_deg = ((theta_half / (M_PI/2.0))*BRDF_SAMPLING_RES_THETA_H);
      double temp = theta_half_deg*BRDF_SAMPLING_RES_THETA_H;
      temp = sqrt(temp);
      int ret_val = (int)temp;
      if (ret_val < 0) ret_val = 0;
      if (ret_val >= BRDF_SAMPLING_RES_THETA_H)
         ret_val = BRDF_SAMPLING_RES_THETA_H-1;
      return ret_val;
   }

   // Lookup theta_diff index
   // In:  [0 .. pi/2]
   // Out: [0 .. 89]
   inline int theta_diff_index(double theta_diff) const
   {
      int tmp = int(theta_diff / (M_PI * 0.5) * BRDF_SAMPLING_RES_THETA_D);
      if (tmp < 0)
         return 0;
      else if (tmp < BRDF_SAMPLING_RES_THETA_D - 1)
         return tmp;
      else
         return BRDF_SAMPLING_RES_THETA_D - 1;
   }

   // Lookup phi_diff index
   inline int phi_diff_index(double phi_diff) const
   {
      // Because of reciprocity, the BRDF is unchanged under
      // phi_diff -> phi_diff + M_PI
      if (phi_diff < 0.0)
         phi_diff += M_PI;

      // In: phi_diff in [0 .. pi]
      // Out: tmp in [0 .. 179]
      int tmp = int(phi_diff / M_PI * BRDF_SAMPLING_RES_PHI_D / 2);
      if (tmp < 0)
         return 0;
      else if (tmp < BRDF_SAMPLING_RES_PHI_D / 2 - 1)
         return tmp;
      else
         return BRDF_SAMPLING_RES_PHI_D / 2 - 1;
   }

   // Given a pair of incoming/outgoing angles, look up the BRDF.
   void lookup_brdf_val(double theta_in, double fi_in,
                        double theta_out, double fi_out,
                        double& red_val, double& green_val,
                        double& blue_val) const
   {
      // Convert to halfangle / difference angle coordinates
      double theta_half, fi_half, theta_diff, fi_diff;

      std_coords_to_half_diff_coords(theta_in, fi_in, theta_out, fi_out,
            theta_half, fi_half, theta_diff, fi_diff);

      // Find index.
      // Note that phi_half is ignored, since isotropic BRDFs are assumed
      int ind = phi_diff_index(fi_diff) +
         theta_diff_index(theta_diff) * BRDF_SAMPLING_RES_PHI_D / 2 +
         theta_half_index(theta_half) * BRDF_SAMPLING_RES_PHI_D / 2 *
         BRDF_SAMPLING_RES_THETA_D;

      red_val = brdf[ind] * RED_SCALE;
      green_val = brdf[ind + BRDF_SAMPLING_RES_THETA_H*BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D/2] * GREEN_SCALE;
      blue_val = brdf[ind + BRDF_SAMPLING_RES_THETA_H*BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D] * BLUE_SCALE;

      if (red_val < 0.0 || green_val < 0.0 || blue_val < 0.0)
         fprintf(stderr, "Below horizon.\n");

   }

//#include "EXR_IO.h"
//
//int main(int argc, char *argv[])
//{
//   if(argc != 3) {
//      fprintf(stderr, "Uncorrect number of arguments to the command line");
//      fprintf(stderr, "Usage: brdf_read [input].binary [output].exr");
//      return 1;
//   }
//
//
//	const char *filename = argv[1];
//	double* brdf;
//
//	// read brdf
//	if (!read_brdf(filename, brdf))
//	{
//		fprintf(stderr, "Error reading %s\n", filename);
//		exit(1);
//	}
//
//   const auto phi      = 90;
//
//   const auto SKIP_PHI = BRDF_SAMPLING_RES_PHI_D / 2;
//
//   const auto SKIP     = SKIP_PHI
//                       * BRDF_SAMPLING_RES_THETA_D
//                       * BRDF_SAMPLING_RES_THETA_H;
//
//   const auto RES      = BRDF_SAMPLING_RES_THETA_H
//                       * BRDF_SAMPLING_RES_THETA_D;
//
//
//   double* img = new double[RES*3];
//
//   for(auto i=0; i<BRDF_SAMPLING_RES_THETA_H; ++i) {
//      for(auto j=0; j<BRDF_SAMPLING_RES_THETA_D; ++j) {
//
//         const auto ind = phi
//                        + j * SKIP_PHI
//                        + i * SKIP_PHI * BRDF_SAMPLING_RES_THETA_D;
//
//         const auto R = brdf[ind + 0*SKIP] * RED_SCALE;
//         const auto G = brdf[ind + 1*SKIP] * GREEN_SCALE;
//         const auto B = brdf[ind + 2*SKIP] * BLUE_SCALE;
//
//         img[3*(i + j*BRDF_SAMPLING_RES_THETA_H)+0] = R;
//         img[3*(i + j*BRDF_SAMPLING_RES_THETA_H)+1] = G;
//         img[3*(i + j*BRDF_SAMPLING_RES_THETA_H)+2] = B;
//
//      }
//   }
//
//   t_EXR_IO<double>::SaveEXR(argv[2], BRDF_SAMPLING_RES_THETA_H, BRDF_SAMPLING_RES_THETA_D, img);
//
//	return 0;
//}

};


/*
anharmonic_core.cpp

Copyright (c) 2014 Terumasa Tadano

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

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

#ifdef _OPENMP

#include <omp.h>

#endif

using namespace PHON_NS;

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

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

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

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

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

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

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

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

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

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

    double mat_convert[3][3];

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

    allocate(xshift_s, 27, 3);

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

    unsigned int icell = 0;

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

                ++icell;

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

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

    unsigned int icount = 0;

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

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

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

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

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

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

    deallocate(xshift_s);
}

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

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

    number_of_groups = 0;

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

    for (const auto &it: fcs_in) {

        arr_tmp.clear();

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

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

    allocate(fcs_group_out, number_of_groups);

    int igroup = -1;

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

    for (const auto &it: fcs_in) {

        arr_tmp.clear();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const auto tune_type_now = phase_storage_in->get_tune_type();

    if (tune_type_now == 1) {

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

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

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

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

    } else if (tune_type_now == 2) {

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

        double phase3[3];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const auto tune_type_now = phase_storage_in->get_tune_type();

    if (tune_type_now == 1) {

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

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

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

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

    } else if (tune_type_now == 2) {

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

        double phase3[3];

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

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

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

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

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

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

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

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

    unsigned int ielem = 0;

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

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

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

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

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

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

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

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

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

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

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

    int k1, k2;

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

    double multi;

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

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

    double f1, f2;

    const auto epsilon = integration->epsilon;

    std::vector<KsListGroup> triplet;

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

    const auto npair_uniq = triplet.size();

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

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

        arr[0] = ns * knum_minus + is_in;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    double ret_tmp;

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

    std::vector<KsListGroup> triplet;

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

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

    const auto npair_uniq = triplet.size();

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

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

    allocate(kmap_identity, nk);

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

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

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

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

                // Prepare two-phonon frequency for the tetrahedron method

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

                k2 = kmesh_in->get_knum(xk_tmp);

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

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

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

        deallocate(energy_tmp);
        deallocate(weight_tetra);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    allocate(invsqrt_mass_p, system->natmin);

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

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

    deallocate(invsqrt_mass_p);
}

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

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

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

    allocate(invsqrt_mass_p, system->natmin);

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

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

    deallocate(invsqrt_mass_p);
}

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

    if (use_tuned_ver) {

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

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

        tune_type = 1;

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

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

        int ii, jj, kk;

        if (tune_type == 1) {

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

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

        } else if (tune_type == 2) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const auto npair_uniq = triplet.size();

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

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

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

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

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

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

    vk_l.clear();

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

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

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

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

        int ik_now = vk_l[ik];

        if (ik_now == -1) {

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

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

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

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

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

    if (mympi->my_rank == 0) {

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

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

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

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

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

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

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

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

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

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

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

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

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