/*

   Copyright (c) 2006-2010, The Scripps Research Institute

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   Author: Dr. Oleg Trott <ot14@columbia.edu>,
           The Olson Lab,
           The Scripps Research Institute

*/

#include "monte_carlo.h"
#include "coords.h"
#include "mutate.h"
#include "quasi_newton.h"

#include "commonMacros.h"
#include "wrapcl.h"
#include "random.h"
#include <iostream>

#include <fstream>
#include <boost/progress.hpp>
#include <thread>

//#define DISPLAY_ANALYSIS
//#define DATA_DISTRIBUTION_TEST
//#define BUILD_KERNEL_FROM_SOURCE
output_type monte_carlo::operator()(model& m, const precalculate& p, const igrid& ig, const precalculate& p_widened, const igrid& ig_widened, const vec& corner1, const vec& corner2, incrementable* increment_me, rng& generator) const {
	output_container tmp;
	this->operator()(m, tmp, p, ig, p_widened, ig_widened, corner1, corner2, increment_me, generator); // call the version that produces the whole container
	VINA_CHECK(!tmp.empty());
	return tmp.front();
}

bool metropolis_accept(fl old_f, fl new_f, fl temperature, rng& generator) {
	if(new_f < old_f) return true;
	const fl acceptance_probability = std::exp((old_f - new_f) / temperature);
	return random_fl(0, 1, generator) < acceptance_probability;
}

void monte_carlo::single_run(model& m, output_type& out, const precalculate& p, const igrid& ig, rng& generator) const {
	conf_size s = m.get_size();
	change g(s);
	vec authentic_v(1000, 1000, 1000);
	out.e = max_fl;
	output_type current(out);
	quasi_newton quasi_newton_par; quasi_newton_par.max_steps = ssd_par.evals;
	VINA_U_FOR(step, num_steps) {
		output_type candidate(current.c, max_fl);
		mutate_conf(candidate.c, m, mutation_amplitude, generator);
		quasi_newton_par(m, p, ig, candidate, g, hunt_cap);
		if(step == 0 || metropolis_accept(current.e, candidate.e, temperature, generator)) {
			quasi_newton_par(m, p, ig, candidate, g, authentic_v);
			current = candidate;
			if(current.e < out.e)
				out = current;
		}
	}
	quasi_newton_par(m, p, ig, out, g, authentic_v);
}

void monte_carlo::many_runs(model& m, output_container& out, const precalculate& p, const igrid& ig, const vec& corner1, const vec& corner2, sz num_runs, rng& generator) const {
	conf_size s = m.get_size();
	VINA_FOR(run, num_runs) {
		output_type tmp(s, 0);
		tmp.c.randomize(corner1, corner2, generator);
		single_run(m, tmp, p, ig, generator);
		out.push_back(new output_type(tmp));
	}
	out.sort();
}

std::vector<output_type> monte_carlo::cl_to_vina(output_type_cl result_ptr[], int exhaus) const {
	std::vector<output_type> results_vina;
	for (int i = 0; i < exhaus; i++) {
		output_type_cl tmp_cl = result_ptr[i];
		conf tmp_c;
		tmp_c.ligands.resize(1);
		// Position
		for (int j = 0; j < 3; j++)tmp_c.ligands[0].rigid.position[j] = tmp_cl.position[j];
		// Orientation
		qt q(tmp_cl.orientation[0], tmp_cl.orientation[1], tmp_cl.orientation[2], tmp_cl.orientation[3]);
		tmp_c.ligands[0].rigid.orientation = q;
		output_type tmp_vina(tmp_c, tmp_cl.e);
		// torsion
		for (int j = 0; j < tmp_cl.lig_torsion_size; j++)tmp_vina.c.ligands[0].torsions.push_back(tmp_cl.lig_torsion[j]);
		// coords
		for (int j = 0; j < MAX_NUM_OF_ATOMS; j++) {
			vec v_tmp(tmp_cl.coords[j][0], tmp_cl.coords[j][1], tmp_cl.coords[j][2]);
			if (v_tmp[0] * v_tmp[1] * v_tmp[2] != 0) tmp_vina.coords.push_back(v_tmp);
		}
		results_vina.push_back(tmp_vina);
	}
	return results_vina;
}

void monte_carlo::generate_uniform_position(const vec corner1, const vec corner2, std::vector<vec>& uniform_data, int exhaustiveness) const{
	assert(exhaustiveness == 125);
	int counter = 0;
	int n = 5;
	for (int i = 0; i < n; i++) {
		double p0 = (corner1.data[0] - corner2.data[0]) / n * i + corner2.data[0];
		for (int j = 0; j < n; j++) {
			double p1 = (corner1.data[1] - corner2.data[1]) / n * j + corner2.data[1];
			for (int k = 0; k < n; k++) {
				double p2 = (corner1.data[2] - corner2.data[2]) / n * k + corner2.data[2];
				uniform_data[counter] = vec(p0, p1, p2);
				counter++;
			}
		}
	}
	assert(counter == exhaustiveness);
}

#ifndef OPENCL_PART_2
// out is sorted
void monte_carlo::operator()(model& m, output_container& out, const precalculate& p, const igrid& ig, const precalculate& p_widened, const igrid& ig_widened, const vec& corner1, const vec& corner2, incrementable* increment_me, rng& generator) const {
	vec authentic_v(1000, 1000, 1000); // FIXME? this is here to avoid max_fl/max_fl
	conf_size s = m.get_size();
	change g(s);
	output_type tmp(s, 0);
	tmp.c.randomize(corner1, corner2, generator);
	fl best_e = max_fl;
	quasi_newton quasi_newton_par; quasi_newton_par.max_steps = ssd_par.evals;
	output_type origin = tmp;

	VINA_U_FOR(step, num_steps) {
		if (increment_me)
			++(*increment_me);
		output_type candidate = tmp;
		mutate_conf(candidate.c, m, mutation_amplitude, generator);
		quasi_newton_par(m, p, ig, candidate, g, hunt_cap);
		if (step == 0 || metropolis_accept(tmp.e, candidate.e, temperature, generator)) {
			tmp = candidate;

			m.set(tmp.c); // FIXME? useless?

			// FIXME only for very promising ones
			if (tmp.e < best_e || out.size() < num_saved_mins) {
				quasi_newton_par(m, p, ig, tmp, g, authentic_v);
				m.set(tmp.c); // FIXME? useless?
				tmp.coords = m.get_heavy_atom_movable_coords();
				add_to_output_container(out, tmp, min_rmsd, num_saved_mins); // 20 - max size
				if (tmp.e < best_e)
					best_e = tmp.e;
			}
		}
	}
	VINA_CHECK(!out.empty());
	VINA_CHECK(out.front().e <= out.back().e); // make sure the sorting worked in the correct order
}
#else

volatile bool finished = false; //20211119 Glinttsd
void print_process() {
	int count = 0;
	printf("\n");
	do
	{
#ifdef WIN32
		Sleep(100);
#else
		sleep(1);
#endif
		printf("\rPerform docking|");
		for (int i = 0; i < count; i++)printf(" ");
		printf("=======");
		for (int i = 0; i < 30 - count; i++)printf(" ");
		printf("|"); fflush(stdout);

		count++;
		count %= 30;
	} while (finished != true);
	printf("\rPerform docking|");
	for (int i = 0; i < 16; i++)printf("=");
	printf("done");
	for (int i = 0; i < 17; i++)printf("=");
	printf("|\n"); fflush(stdout);
}

void monte_carlo::operator()(model& m, output_container& out, const precalculate& p, const igrid& ig, const precalculate& p_widened, const igrid& ig_widened, const vec& corner1, const vec& corner2, incrementable* increment_me, rng& generator) const {
	/**************************************************************************/
	/***************************    OpenCL Init    ****************************/
	/**************************************************************************/

	cl_int err;
	cl_platform_id* platforms;
	cl_device_id* devices;
	cl_context context;
	cl_command_queue queue;
	cl_int gpu_platform_id;
	SetupPlatform(&platforms, &gpu_platform_id);
	SetupDevice(platforms, &devices, gpu_platform_id);
	SetupContext(platforms, devices, &context, 1, gpu_platform_id);
	SetupQueue(&queue, context, devices);
	char* program_file;
	cl_program program_cl;
	cl_program program;
	size_t program_size;

	//Read kernel source code
#ifdef BUILD_KERNEL_FROM_SOURCE
	printf("\nBuild kernels from source"); fflush(stdout);
	const std::string default_work_path = ".";
	const std::string include_path = default_work_path + "/OpenCL/inc"; //FIX it
	const std::string addtion = "";

	char* program_file_n[NUM_OF_FILES];
	size_t program_size_n[NUM_OF_FILES];
	std::string file_paths[NUM_OF_FILES] = {	default_work_path + "/OpenCL/src/kernels/code_head.cpp",
												default_work_path + "/OpenCL/src/kernels/mutate_conf.cpp",
												default_work_path + "/OpenCL/src/kernels/matrix.cpp",
												default_work_path + "/OpenCL/src/kernels/quasi_newton.cpp",
												default_work_path + "/OpenCL/src/kernels/kernel2.cl"}; // The order of files is important!

	read_n_file(program_file_n, program_size_n, file_paths, NUM_OF_FILES);
	std::string final_file;
	size_t final_size = NUM_OF_FILES - 1; // count '\n'
	for (int i = 0; i < NUM_OF_FILES; i++) {
		if (i == 0) final_file = program_file_n[0];
		else final_file = final_file + '\n' + (std::string)program_file_n[i];
		final_size += program_size_n[i];
	}
	const char* final_files_char = final_file.data();

	program_cl = clCreateProgramWithSource(context, 1, (const char**)&final_files_char, &final_size, &err); checkErr(err);
	SetupBuildProgramWithSource(program_cl, NULL, devices, include_path, addtion);
	SaveProgramToBinary(program_cl, "Kernel2_Opt.bin");
#endif

	//Display the progress
	printf("\nSearch depth is set to %d",search_depth);
	std::thread console_thread(print_process);

	program_cl = SetupBuildProgramWithBinary(context, devices, "Kernel2_Opt.bin");

	err = clUnloadPlatformCompiler(platforms[gpu_platform_id]); checkErr(err);
	//Set kernel arguments
	cl_kernel kernels[1];
	char kernel_name[][50] = { "kernel2" };
	SetupKernel(kernels, program_cl, 1, kernel_name);
	size_t max_wg_size; // max work item within one work group
	size_t max_wi_size[3]; // max work item within each dimension(global)
	err = clGetDeviceInfo(devices[0], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(size_t), &max_wg_size, NULL); checkErr(err);
	err = clGetDeviceInfo(devices[0], CL_DEVICE_MAX_WORK_ITEM_SIZES, 3 * sizeof(size_t), &max_wi_size, NULL); checkErr(err);

	/**************************************************************************/
	/************************    Original Vina code    ************************/
	/**************************************************************************/

	vec authentic_v(1000, 1000, 1000); // FIXME? this is here to avoid max_fl/max_fl
	conf_size s = m.get_size();
	change g(s);
	output_type tmp(s, 0);
	quasi_newton quasi_newton_par; const int quasi_newton_par_max_steps = ssd_par.evals;

	/**************************************************************************/
	/************************    Allocate CPU memory    ***********************/
	/**************************************************************************/

	// Preparing m related data
	m_cl m_cl;
	assert(m.atoms.size() < MAX_NUM_OF_ATOMS);

	for (int i = 0; i < m.atoms.size(); i++) {
		m_cl.atoms[i].types[0] = m.atoms[i].el;// To store 4 atoms types (el, ad, xs, sy)
		m_cl.atoms[i].types[1] = m.atoms[i].ad;
		m_cl.atoms[i].types[2] = m.atoms[i].xs;
		m_cl.atoms[i].types[3] = m.atoms[i].sy;
		for (int j = 0; j < 3; j++) {
			m_cl.atoms[i].coords[j] = m.atoms[i].coords[j];// To store atom coords
		}
	}

	// To store atoms coords
	for (int i = 0; i < m.coords.size(); i++) {
		for (int j = 0; j < 3; j++) {
			m_cl.m_coords.coords[i][j] = m.coords[i].data[j];
		}
	}

	//To store minus forces
	for (int i = 0; i < m.coords.size(); i++) {
		for (int j = 0; j < 3; j++) {
			m_cl.minus_forces.coords[i][j] = m.minus_forces[i].data[j];
		}
	}

	// Preparing ligand data
	assert(m.num_other_pairs() == 0); // m.other_paris is not supported!
	assert(m.ligands.size() == 1); // Only one ligand supported!
	m_cl.ligand.pairs.num_pairs = m.ligands[0].pairs.size();
	for (int i = 0; i < m_cl.ligand.pairs.num_pairs; i++) {
		m_cl.ligand.pairs.type_pair_index[i]	= m.ligands[0].pairs[i].type_pair_index;
		m_cl.ligand.pairs.a[i]					= m.ligands[0].pairs[i].a;
		m_cl.ligand.pairs.b[i]					= m.ligands[0].pairs[i].b;
	}
	m_cl.ligand.begin = m.ligands[0].begin; // 0
	m_cl.ligand.end = m.ligands[0].end; // 29
	ligand m_ligand = m.ligands[0]; // Only support one ligand
	assert(m_ligand.end < MAX_NUM_OF_ATOMS);

	// Store root node
	m_cl.ligand.rigid.atom_range[0][0] = m_ligand.node.begin;
	m_cl.ligand.rigid.atom_range[0][1] = m_ligand.node.end;
	for (int i = 0; i < 3; i++) m_cl.ligand.rigid.origin[0][i] = m_ligand.node.get_origin()[i];
	for (int i = 0; i < 9; i++) m_cl.ligand.rigid.orientation_m[0][i] = m_ligand.node.get_orientation_m().data[i];
	m_cl.ligand.rigid.orientation_q[0][0] = m_ligand.node.orientation().R_component_1();
	m_cl.ligand.rigid.orientation_q[0][1] = m_ligand.node.orientation().R_component_2();
	m_cl.ligand.rigid.orientation_q[0][2] = m_ligand.node.orientation().R_component_3();
	m_cl.ligand.rigid.orientation_q[0][3] = m_ligand.node.orientation().R_component_4();
	for (int i = 0; i < 3; i++) {m_cl.ligand.rigid.axis[0][i] = 0;m_cl.ligand.rigid.relative_axis[0][i] = 0;m_cl.ligand.rigid.relative_origin[0][i] = 0;}

	// Store children nodes (in depth-first order)
	struct tmp_struct {
		int start_index = 0;
		int parent_index = 0;
		void store_node(tree<segment>& child_ptr, rigid_cl& rigid) {
			start_index++; // start with index 1, index 0 is root node
			rigid.parent[start_index] = parent_index;
			rigid.atom_range[start_index][0] = child_ptr.node.begin;
			rigid.atom_range[start_index][1] = child_ptr.node.end;
			for (int i = 0; i < 9; i++) rigid.orientation_m[start_index][i] = child_ptr.node.get_orientation_m().data[i];
			rigid.orientation_q[start_index][0] = child_ptr.node.orientation().R_component_1();
			rigid.orientation_q[start_index][1] = child_ptr.node.orientation().R_component_2();
			rigid.orientation_q[start_index][2] = child_ptr.node.orientation().R_component_3();
			rigid.orientation_q[start_index][3] = child_ptr.node.orientation().R_component_4();
			for (int i = 0; i < 3; i++) {
				rigid.origin[start_index][i] = child_ptr.node.get_origin()[i];
				rigid.axis[start_index][i] = child_ptr.node.get_axis()[i];
				rigid.relative_axis[start_index][i] = child_ptr.node.relative_axis[i];
				rigid.relative_origin[start_index][i] = child_ptr.node.relative_origin[i];
			}
			if (child_ptr.children.size() == 0) return;
			else {
				assert(start_index < MAX_NUM_OF_RIGID);
				int parent_index_tmp = start_index;
				for (int i = 0; i < child_ptr.children.size(); i++) {
					this->parent_index = parent_index_tmp; // Update parent index
					this->store_node(child_ptr.children[i], rigid);
				}
			}
		}
	};
	tmp_struct ts;
	for (int i = 0; i < m_ligand.children.size(); i++) {
		ts.parent_index = 0; // Start a new branch, whose parent is 0
		ts.store_node(m_ligand.children[i], m_cl.ligand.rigid);
	}
	m_cl.ligand.rigid.num_children = ts.start_index;

	// set children_map
	for (int i = 0; i < MAX_NUM_OF_RIGID; i++)
		for (int j = 0; j < MAX_NUM_OF_RIGID; j++)
			m_cl.ligand.rigid.children_map[i][j] = false;
	for (int i = 1; i < m_cl.ligand.rigid.num_children + 1; i++) {
		int parent_index = m_cl.ligand.rigid.parent[i];
		m_cl.ligand.rigid.children_map[parent_index][i] = true;
	}
	m_cl.m_num_movable_atoms = m.num_movable_atoms();
	size_t m_cl_size = sizeof(m_cl);

	// Preparing ig related data
	ig_cl* ig_cl_ptr = (ig_cl*)malloc(sizeof(ig_cl));
	ig_cl_ptr->atu = ig.get_atu(); // atu
	ig_cl_ptr->slope = ig.get_slope(); // slope
	std::vector<grid> tmp_grids = ig.get_grids();
	int grid_size = tmp_grids.size();
	assert(GRIDS_SIZE == grid_size); // grid_size has to be 17

	for (int i = 0; i < grid_size; i++) {
		for (int j = 0; j < 3; j++) {
			ig_cl_ptr->grids[i].m_init[j] = tmp_grids[i].m_init[j];
			ig_cl_ptr->grids[i].m_factor[j] = tmp_grids[i].m_factor[j];
			ig_cl_ptr->grids[i].m_dim_fl_minus_1[j] = tmp_grids[i].m_dim_fl_minus_1[j];
			ig_cl_ptr->grids[i].m_factor_inv[j] = tmp_grids[i].m_factor_inv[j];
		}
		if (tmp_grids[i].m_data.dim0() != 0) {
			ig_cl_ptr->grids[i].m_i = tmp_grids[i].m_data.dim0(); assert(MAX_NUM_OF_GRID_MI >= ig_cl_ptr->grids[i].m_i);
			ig_cl_ptr->grids[i].m_j = tmp_grids[i].m_data.dim1(); assert(MAX_NUM_OF_GRID_MJ >= ig_cl_ptr->grids[i].m_j);
			ig_cl_ptr->grids[i].m_k = tmp_grids[i].m_data.dim2(); assert(MAX_NUM_OF_GRID_MK >= ig_cl_ptr->grids[i].m_k);

			//for (int j = 0; j < ig_cl_ptr->grids[i].m_i * ig_cl_ptr->grids[i].m_j * ig_cl_ptr->grids[i].m_k; j++) {
			//	ig_cl_ptr->grids[i].m_data[j] = tmp_grids[i].m_data.m_data[j];
			//}

			int m_i = tmp_grids[i].m_data.dim0();
			int m_j = tmp_grids[i].m_data.dim1();
			int m_k = tmp_grids[i].m_data.dim2();
			for (sz idx0 = 0; idx0 < m_i - 1; idx0++)
				for (sz idx1 = 0; idx1 < m_j - 1; idx1++)
					for (sz idx2 = 0; idx2 < m_k - 1; idx2++) {
						int base = (idx0 + m_i * (idx1 + m_j * idx2)) * 8;
						ig_cl_ptr->grids[i].m_data[base] = tmp_grids[i].m_data(idx0, idx1, idx2); // f000
						ig_cl_ptr->grids[i].m_data[base + 1] = tmp_grids[i].m_data(idx0 + 1, idx1, idx2); // f100
						ig_cl_ptr->grids[i].m_data[base + 2] = tmp_grids[i].m_data(idx0, idx1 + 1, idx2); // f010
						ig_cl_ptr->grids[i].m_data[base + 3] = tmp_grids[i].m_data(idx0 + 1, idx1 + 1, idx2); // f110
						ig_cl_ptr->grids[i].m_data[base + 4] = tmp_grids[i].m_data(idx0, idx1, idx2 + 1); // f001
						ig_cl_ptr->grids[i].m_data[base + 5] = tmp_grids[i].m_data(idx0 + 1, idx1, idx2 + 1); // f101
						ig_cl_ptr->grids[i].m_data[base + 6] = tmp_grids[i].m_data(idx0, idx1 + 1, idx2 + 1); // f011
						ig_cl_ptr->grids[i].m_data[base + 7] = tmp_grids[i].m_data(idx0 + 1, idx1 + 1, idx2 + 1); // f111
					}

		}
		else {
			ig_cl_ptr->grids[i].m_i = 0;
			ig_cl_ptr->grids[i].m_j = 0;
			ig_cl_ptr->grids[i].m_k = 0;
		}
	}
	size_t ig_cl_size = sizeof(*ig_cl_ptr);

	// Generating random ligand structures
	std::vector<output_type_cl*> rand_molec_struc_vec; rand_molec_struc_vec.resize(thread);
	for (int i = 0; i < thread; i++) {
		rand_molec_struc_vec[i] = (output_type_cl*)malloc(sizeof(output_type_cl));
	}

	//std::vector<output_type_cl> rand_molec_struc_vec;
	//rand_molec_struc_vec.resize(thread);
	int lig_torsion_size = tmp.c.ligands[0].torsions.size();
	int flex_torsion_size; if (tmp.c.flex.size() != 0) flex_torsion_size = tmp.c.flex[0].torsions.size(); else flex_torsion_size = 0;
	std::vector<vec> uniform_data;
	uniform_data.resize(thread);

	for (int i = 0; i < thread; i++) {
		tmp.c.randomize(corner1, corner2, generator); // generate a random structure
		//Generate positions with uniform probability
		//generate_uniform_position(corner1, corner2, uniform_data, exhaustiveness);
		//for (int j = 0; j < 3; j++) rand_molec_struc_vec[i].position[j] = uniform_data[i].data[j];
		for (int j = 0; j < 3; j++) rand_molec_struc_vec[i]->position[j] = tmp.c.ligands[0].rigid.position[j];
		assert(lig_torsion_size < MAX_NUM_OF_LIG_TORSION);
		for (int j = 0; j < lig_torsion_size; j++) rand_molec_struc_vec[i]->lig_torsion[j] = tmp.c.ligands[0].torsions[j];// Only support one ligand
		assert(flex_torsion_size < MAX_NUM_OF_FLEX_TORSION);
		for (int j = 0; j < flex_torsion_size; j++) rand_molec_struc_vec[i]->flex_torsion[j] = tmp.c.flex[0].torsions[j];// Only support one flex

		rand_molec_struc_vec[i]->orientation[0] = (float)tmp.c.ligands[0].rigid.orientation.R_component_1();
		rand_molec_struc_vec[i]->orientation[1] = (float)tmp.c.ligands[0].rigid.orientation.R_component_2();
		rand_molec_struc_vec[i]->orientation[2] = (float)tmp.c.ligands[0].rigid.orientation.R_component_3();
		rand_molec_struc_vec[i]->orientation[3] = (float)tmp.c.ligands[0].rigid.orientation.R_component_4();

		rand_molec_struc_vec[i]->lig_torsion_size = lig_torsion_size;
	}

	// Preaparing p related data
	p_cl p_cl;
	p_cl.m_cutoff_sqr = p.cutoff_sqr();
	p_cl.factor = p.factor;
	p_cl.n = p.n;
	assert(MAX_P_DATA_M_DATA_SIZE > p.data.m_data.size());
	for (int i = 0; i < p.data.m_data.size(); i++) {
		p_cl.m_data[i].factor = p.data.m_data[i].factor;
		assert(FAST_SIZE == p.data.m_data[i].fast.size());
		assert(SMOOTH_SIZE == p.data.m_data[i].smooth.size());
		for (int j = 0; j < FAST_SIZE; j++) {
			p_cl.m_data[i].fast[j] = p.data.m_data[i].fast[j];
		}
		for (int j = 0; j < SMOOTH_SIZE; j++) {
			p_cl.m_data[i].smooth[j][0] = p.data.m_data[i].smooth[j].first;
			p_cl.m_data[i].smooth[j][1] = p.data.m_data[i].smooth[j].second;
		}
	}
	size_t p_cl_size = sizeof(p_cl);

	// Generate random maps
	random_maps* rand_maps = (random_maps*)malloc(sizeof(random_maps));
	for (int i = 0; i < MAX_NUM_OF_RANDOM_MAP; i++) {
		rand_maps->int_map[i] = random_int(0, int(lig_torsion_size), generator);
		rand_maps->pi_map[i] = random_fl(-pi, pi, generator);
	}
	for (int i = 0; i < MAX_NUM_OF_RANDOM_MAP; i++) {
		vec rand_coords = random_inside_sphere(generator);
		for (int j = 0; j < 3 ; j ++) {
			rand_maps->sphere_map[i][j] = rand_coords[j];
		}
	}
	size_t rand_maps_size = sizeof(*rand_maps);

	float hunt_cap_float[3] = {hunt_cap[0], hunt_cap[1], hunt_cap[2]};
	float authentic_v_float[3] = { authentic_v[0],authentic_v[1], authentic_v[2] };
	float mutation_amplitude_float = mutation_amplitude;
	float epsilon_fl_float = epsilon_fl;
	int	total_wi = max_wi_size[0] * max_wi_size[1];
	/**************************************************************************/
	/************************    Allocate GPU memory    ***********************/
	/**************************************************************************/

	cl_mem rand_molec_struc_vec_gpu;
	CreateDeviceBuffer(&rand_molec_struc_vec_gpu, CL_MEM_READ_ONLY, thread* SIZE_OF_MOLEC_STRUC, context);
	for (int i = 0; i < thread; i++) {
		std::vector<float> pos(rand_molec_struc_vec[i]->position, rand_molec_struc_vec[i]->position + 3);
		std::vector<float> ori(rand_molec_struc_vec[i]->orientation, rand_molec_struc_vec[i]->orientation + 4);
		std::vector<float> lig_tor(rand_molec_struc_vec[i]->lig_torsion, rand_molec_struc_vec[i]->lig_torsion + MAX_NUM_OF_LIG_TORSION);
		std::vector<float> flex_tor(rand_molec_struc_vec[i]->flex_torsion, rand_molec_struc_vec[i]->flex_torsion + MAX_NUM_OF_FLEX_TORSION);
		float lig_tor_size = rand_molec_struc_vec[i]->lig_torsion_size;
		err = clEnqueueWriteBuffer(	queue, rand_molec_struc_vec_gpu, false, i * SIZE_OF_MOLEC_STRUC,
									pos.size() * sizeof(float), pos.data(), 0, NULL, NULL); checkErr(err);
		err = clEnqueueWriteBuffer(	queue, rand_molec_struc_vec_gpu, false, i * SIZE_OF_MOLEC_STRUC + pos.size() * sizeof(float),
									ori.size() * sizeof(float), ori.data(), 0, NULL, NULL); checkErr(err);
		err = clEnqueueWriteBuffer(	queue, rand_molec_struc_vec_gpu, false, i * SIZE_OF_MOLEC_STRUC + (pos.size() + ori.size() ) * sizeof(float),
									lig_tor.size() * sizeof(float), lig_tor.data(), 0, NULL, NULL); checkErr(err);
		err = clEnqueueWriteBuffer(queue, rand_molec_struc_vec_gpu, false, i * SIZE_OF_MOLEC_STRUC + (pos.size() + ori.size() + MAX_NUM_OF_LIG_TORSION) * sizeof(float),
									flex_tor.size() * sizeof(float), flex_tor.data(), 0, NULL, NULL); checkErr(err);
		err = clEnqueueWriteBuffer(queue, rand_molec_struc_vec_gpu, false, i * SIZE_OF_MOLEC_STRUC + (pos.size() + ori.size() + MAX_NUM_OF_LIG_TORSION + MAX_NUM_OF_FLEX_TORSION) * sizeof(float),
			sizeof(float), &lig_tor_size, 0, NULL, NULL); checkErr(err);
	}

	cl_mem best_e_gpu;
	CreateDeviceBuffer(&best_e_gpu, CL_MEM_READ_WRITE, thread * sizeof(float), context);
	err = clEnqueueFillBuffer(queue, best_e_gpu, &max_fl, sizeof(float), 0, thread * sizeof(float), 0, NULL, NULL); checkErr(err);

	cl_mem rand_maps_gpu;
	CreateDeviceBuffer(&rand_maps_gpu, CL_MEM_READ_ONLY, rand_maps_size, context);
	err = clEnqueueWriteBuffer(	queue, rand_maps_gpu, false, 0,	rand_maps_size, rand_maps, 0, NULL, NULL); checkErr(err);

	cl_mem hunt_cap_gpu;
	CreateDeviceBuffer(&hunt_cap_gpu, CL_MEM_READ_ONLY, 3 * sizeof(float), context);
	err = clEnqueueWriteBuffer(queue, hunt_cap_gpu, false, 0, 3 * sizeof(float), hunt_cap_float, 0, NULL, NULL); checkErr(err);

	// Preparing m related data
	cl_mem m_cl_gpu;
	CreateDeviceBuffer(&m_cl_gpu, CL_MEM_READ_WRITE, m_cl_size, context);
	err = clEnqueueWriteBuffer(queue, m_cl_gpu, false, 0, m_cl_size, &m_cl, 0, NULL, NULL); checkErr(err);

	// Preparing p related data
	cl_mem p_cl_gpu;
	CreateDeviceBuffer(&p_cl_gpu, CL_MEM_READ_ONLY, p_cl_size, context);
	err = clEnqueueWriteBuffer(queue, p_cl_gpu, false, 0, p_cl_size, &p_cl, 0, NULL, NULL); checkErr(err);

	// Preparing ig related data (cache related data)
	cl_mem ig_cl_gpu;
	CreateDeviceBuffer(&ig_cl_gpu, CL_MEM_READ_ONLY, ig_cl_size, context);
	err = clEnqueueWriteBuffer(queue, ig_cl_gpu, false, 0, ig_cl_size, ig_cl_ptr, 0, NULL, NULL); checkErr(err);

	cl_mem authentic_v_gpu;
	CreateDeviceBuffer(&authentic_v_gpu, CL_MEM_READ_ONLY, 3 * sizeof(float), context);
	err = clEnqueueWriteBuffer(queue, authentic_v_gpu, false, 0, 3 * sizeof(float), authentic_v_float, 0, NULL, NULL); checkErr(err);

	// Preparing result data
	cl_mem results;
	CreateDeviceBuffer(&results, CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR, thread * sizeof(output_type_cl), context);

	clFinish(queue);

	/**************************************************************************/
	/************************   Set kernel arguments    ***********************/
	/**************************************************************************/
	SetKernelArg(kernels[0], 0, sizeof(cl_mem),		&m_cl_gpu);
	SetKernelArg(kernels[0], 1, sizeof(cl_mem),		&ig_cl_gpu);
	SetKernelArg(kernels[0], 2, sizeof(cl_mem),		&p_cl_gpu);
	SetKernelArg(kernels[0], 3,	sizeof(cl_mem),		&rand_molec_struc_vec_gpu);
	SetKernelArg(kernels[0], 4, sizeof(cl_mem),		&best_e_gpu);
	SetKernelArg(kernels[0], 5, sizeof(int),		&quasi_newton_par_max_steps);
	SetKernelArg(kernels[0], 6, sizeof(unsigned int),	&num_steps);
	SetKernelArg(kernels[0], 7, sizeof(float),		&mutation_amplitude_float);
	SetKernelArg(kernels[0], 8, sizeof(cl_mem),		&rand_maps_gpu);
	SetKernelArg(kernels[0], 9, sizeof(float),		&epsilon_fl_float);
	SetKernelArg(kernels[0], 10, sizeof(cl_mem),	&hunt_cap_gpu);
	SetKernelArg(kernels[0], 11, sizeof(cl_mem),	&authentic_v_gpu);
	SetKernelArg(kernels[0], 12, sizeof(cl_mem),	&results);
	SetKernelArg(kernels[0], 13, sizeof(int),		&search_depth);
	SetKernelArg(kernels[0], 14, sizeof(int),		&thread);
	SetKernelArg(kernels[0], 15, sizeof(int),	&total_wi);
	/**************************************************************************/
	/****************************   Start kernel    ***************************/
	/**************************************************************************/
	size_t global_size[2] = {512, 32 };
	size_t local_size[2] = { 16,2 };
	cl_event monte_clarlo_cl;
	err = clEnqueueNDRangeKernel(queue, kernels[0], 2, 0, global_size, local_size, 0, NULL, &monte_clarlo_cl); checkErr(err);

	clWaitForEvents(1, &monte_clarlo_cl);

	finished = true;
	console_thread.join(); // wait the thread finish

	//getchar();

	// Maping result data
 	output_type_cl* result_ptr = (output_type_cl*)clEnqueueMapBuffer(queue, results, CL_TRUE, CL_MAP_READ,
																	0, thread * sizeof(output_type_cl),
																	0, NULL, NULL, &err); checkErr(err);

	std::vector<output_type> result_vina = cl_to_vina(result_ptr, thread);
	// if empty, something goes wrong in the device part
	if (result_vina.size() == 0) {
		printf("Error in the device part\n"); exit(-1);
	}

	// Unmaping result data
	err = clEnqueueUnmapMemObject(queue, results, result_ptr, 0, NULL, NULL); checkErr(err);

	// Write back to vina
	for (int i = 0; i < thread; i++) {
		add_to_output_container(out, result_vina[i], min_rmsd, num_saved_mins);
	}
	VINA_CHECK(!out.empty());
	VINA_CHECK(out.front().e <= out.back().e);

	/**************************************************************************/
	/*******************************   Finish    ******************************/
	/**************************************************************************/
	// Memory objects release
	err = clReleaseMemObject(m_cl_gpu); checkErr(err);
	err = clReleaseMemObject(ig_cl_gpu); checkErr(err);
	err = clReleaseMemObject(p_cl_gpu); checkErr(err);
	err = clReleaseMemObject(rand_molec_struc_vec_gpu); checkErr(err);
	err = clReleaseMemObject(best_e_gpu); checkErr(err);
	err = clReleaseMemObject(hunt_cap_gpu); checkErr(err);
	err = clReleaseMemObject(authentic_v_gpu); checkErr(err);
	err = clReleaseMemObject(results); checkErr(err);

	free(ig_cl_ptr);
	free(rand_maps);
	for (int i = 0; i < thread; i++)free(rand_molec_struc_vec[i]);

	// Output Analysis
	cl_ulong time_start, time_end;
	double total_time;
	err = clGetEventProfilingInfo(monte_clarlo_cl, CL_PROFILING_COMMAND_START, sizeof(time_start), &time_start, NULL); checkErr(err);
	err = clGetEventProfilingInfo(monte_clarlo_cl, CL_PROFILING_COMMAND_END, sizeof(time_end), &time_end, NULL); checkErr(err);
	total_time = time_end - time_start;
	printf("GPU monte carlo runtime: %0.3f s", (total_time / 1000000000.0));

#ifdef DISPLAY_ANALYSIS
	// Output Analysis
	cl_ulong time_start, time_end;
	double total_time;
	err = clGetEventProfilingInfo(monte_clarlo_cl, CL_PROFILING_COMMAND_START, sizeof(time_start), &time_start, NULL); checkErr(err);
	err = clGetEventProfilingInfo(monte_clarlo_cl, CL_PROFILING_COMMAND_END, sizeof(time_end), &time_end, NULL); checkErr(err);
	total_time = time_end - time_start;
	printf("\nGPU monte carlo runtime = %0.3f s\n", (total_time / 1000000000.0));

	std::ofstream file("gpu_runtime.txt");
	if (file.is_open())
	{
		file << "GPU monte carlo runtime = " << (double)(total_time / 1000000000.0) << " s" << std::endl;
		file.close();
	}

	cl_ulong private_mem_size;
	err = clGetKernelWorkGroupInfo(kernels[0], devices[0], CL_KERNEL_PRIVATE_MEM_SIZE, sizeof(cl_ulong), &private_mem_size, NULL); checkErr(err);
	printf("\nprivate mem used = %f KBytes", (double)private_mem_size / 1024);

	cl_ulong local_mem_size;
	err = clGetKernelWorkGroupInfo(kernels[0], devices[0], CL_KERNEL_LOCAL_MEM_SIZE, sizeof(cl_ulong), &local_mem_size, NULL); checkErr(err);
	printf("\nlocal mem used = %d Bytes", (int)local_mem_size);

	printf("\nconstant mem used = %0.3f MBytes \n", (double)(	p_cl_size + ig_cl_size + thread * SIZE_OF_MOLEC_STRUC) / (1024 * 1024));
#endif
	//getchar();
}
#endif // OPENCL_VERSION


// Copyright 2019 Google LLC & Bastiaan Konings
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// written by bastiaan konings schuiling 2008 - 2015
// this work is public domain. the code is undocumented, scruffy, untested, and should generally not be used for anything important.
// i do not offer support, so don't ask. to be used for inspiration :)

#include "main.hpp"

#include "utils.hpp"

#include "systems/graphics/resources/texture.hpp"
#include "managers/resourcemanagerpool.hpp"

#include <boost/algorithm/string.hpp>
#include <cmath>

float GetQuantizedDirectionBias() {
  return GetConfiguration()->GetReal("gameplay_quantizeddirectionbias", _default_QuantizedDirectionBias);
}

void QuantizeDirection(Vector3 &inputDirection, float bias) {

  // digitize input

  Vector3 inputDirectionNorm = inputDirection.GetNormalized(0);

  int directions = GetConfiguration()->GetInt("gameplay_quantizeddirectioncount", 8);

  radian angle = inputDirectionNorm.GetAngle2D();
  angle /= pi * 2.0f;
  angle = std::round(angle * directions);
  angle /= directions;
  angle *= pi * 2.0f;

  inputDirection = (inputDirectionNorm * (1.0 - bias) + (Vector3(1, 0, 0).GetRotated2D(angle) * bias)).GetNormalized(inputDirectionNorm) * inputDirection.GetLength();
}

Vector3 GetProjectedCoord(const Vector3 &pos3D, boost::intrusive_ptr<Camera> camera) {
  Matrix4 rotMat;
  rotMat.ConstructInverse(camera->GetDerivedPosition(), Vector3(1, 1, 1), camera->GetDerivedRotation());
  float fov = camera->GetFOV();

  // cotangent
  float f = 1.0 / tan((fov / 360.0 * pi * 2) / 2.0);

  Vector3 contextSize3D = GetGraphicsSystem()->GetContextSize();
  float aspect = contextSize3D.coords[0] / contextSize3D.coords[1];
  float aspect2D = GetMenuTask()->GetWindowManager()->GetAspectRatio();
  if (aspect2D < aspect) aspect = aspect2D;

  //printf("aspect: %f\n", aspect);
  float zNear = 40.0;
  float zFar = 270.0;

  Matrix4 perspMat;
  perspMat.elements[0] = f / aspect;
  perspMat.elements[5] = f;
  perspMat.elements[10] = (zFar + zNear) / (zNear - zFar);
  perspMat.elements[11] = (2 * zFar * zNear) / (zNear - zFar);
  perspMat.elements[14] = -1;

  Matrix4 resMat = perspMat * rotMat;

  float x, y, z, w;
  resMat.MultiplyVec4(pos3D.coords[0], pos3D.coords[1], pos3D.coords[2], 1, x, y, z, w);

  Vector3 result;
  result.coords[0] = x / w;
  result.coords[1] = y / w;

  //printf("%f %f %f\n", x / w, y / w, z / w);

  result.coords[1] = -result.coords[1];

  result.coords[1] *= aspect2D / aspect;

  result += 1.0;
  result *= 0.5;
  result *= 100;

  return result;
}

int GetVelocityID(e_Velocity velo, bool treatDribbleAsWalk) {
  int id = 0;
  switch (velo) {
    case e_Velocity_Idle:
      id = 0;
      break;
    case e_Velocity_Dribble:
      id = 1;
      break;
    case e_Velocity_Walk:
      id = 2;
      break;
    case e_Velocity_Sprint:
      id = 3;
      break;
    default:
      id = 0;
      break;
  }
  if (treatDribbleAsWalk && id > 1) id--;
  return id;
}

std::map < e_PositionName, std::vector<Stat> > defaultProfiles;

float CalculateStat(float baseStat, float profileStat, float age, e_DevelopmentCurveType developmentCurveType) {

  float idealAge = 27;
  float ageFactor = curve( 1.0f - NormalizedClamp(fabs(age - idealAge), 0, 13) * 0.5f , 1.0f) * 2.0f - 1.0f; // 0 .. 1
  assert(ageFactor >= 0.0f && ageFactor <= 1.0f);
  //ageFactor = clamp(ageFactor, 0.0f, 1.0f);

  // this factor should roughly be around 1.0f for good players at their top age.
  float agedBaseStat = baseStat * (ageFactor * 0.5f + 0.5f) * 1.2f;

  float agedProfileStat = clamp(profileStat * 2.0f * agedBaseStat, 0.01f, 1.0f); // profile stat * 2 because average == 0.5

  return agedProfileStat;
}

template <> TemporalValue<Quaternion>::TemporalValue() {
  data = QUATERNION_IDENTITY;
  time_ms = 0;
};

template <typename T> TemporalSmoother<T>::TemporalSmoother() {
  //snapshotSize = 3;
  snapshotSize =
      3 + int(std::ceil(temporalSmoother_history_ms /
                        10.0f));  // not sure how to calculate proper number?
  values = boost::circular_buffer< TemporalValue<T> >(snapshotSize);
}

template <typename T> void TemporalSmoother<T>::SetValue(const T &data, unsigned long valueTime_ms) {
  TemporalValue<T> value;
  value.data = data;
  value.time_ms = valueTime_ms;
  //printf("adding time: %lu\n", value.time_ms);
  values.push_back(value);
}

template <typename T> T TemporalSmoother<T>::GetValue(unsigned long currentTime_ms, unsigned long history_ms) const {

  if (values.size() == 0) {
    TemporalValue<T> bla; // do it like this so the struct constructor is invoked for T
    return bla.data;
  }
  if (values.size() == 1) return (*values.begin()).data; // only one value yet

  //return (values.back()).data; // disable smoother

  unsigned long now_ms = currentTime_ms;
  //unsigned long now_ms = (currentTime_ms == 0) ? EnvironmentManager::GetInstance().GetTime_ms() : currentTime_ms;
  unsigned long targetTime_ms = 0;
  if (history_ms <= now_ms) targetTime_ms = now_ms - history_ms; // this makes sure targetTime_ms won't become negative (and loop-around since it's an unsigned var)

  // find the 2 values we need

  TemporalValue<T> value1;
  TemporalValue<T> value2;

  int t1index = -1;
  int t2index = -1;
  for (unsigned int i = 0; i < values.size(); i++) {
    const TemporalValue<T> *iter = &values[i];
    if ((*iter).time_ms <= targetTime_ms) {
      value1.data = (*iter).data;
      value1.time_ms = (*iter).time_ms;
      t1index = i;
    }
    else if ((*iter).time_ms > targetTime_ms) {
      value2.data = (*iter).data;
      value2.time_ms = (*iter).time_ms;
      t2index = i;
      break;
    }
  }
  if (value1.time_ms == 0) return value2.data;
  if (value2.time_ms == 0) return value1.data;

  float bias = NormalizedClamp(targetTime_ms, value1.time_ms, std::max(value2.time_ms, value1.time_ms + 1));

  return DataMix(value1.data, value2.data, bias);
}

template <typename T> T TemporalSmoother<T>::DataMix(const T &data1, const T &data2, float bias) const {
  return data1 * (1.0f - bias) + data2 * bias;
}

template <> Quaternion TemporalSmoother<Quaternion>::DataMix(const Quaternion &data1, const Quaternion &data2, float bias) const {
  return data1.GetLerped(bias, data2);
}

template class TemporalSmoother<Vector3>;
template class TemporalSmoother<Quaternion>;
template class TemporalSmoother<float>;
template class TemporalSmoother<unsigned long>;
template class TemporalSmoother<int>;


// -*- C++ -*-
//
// Copyright Sylvain Bougerel 2009 - 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file COPYING or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#define BOOST_TEST_DYN_LINK
#define SPATIAL_ENABLE_ASSERT // detect interal issues that should not occur

#include <boost/test/unit_test.hpp>
#include <utility> // std::make_pair
#include "../../src/box_multimap.hpp"
#include "spatial_test_types.hpp"

using namespace spatial;

BOOST_AUTO_TEST_CASE( test_box_map_constructors )
{
  box_multimap<2, int2, int> boxes;
  box_multimap<0, int2, int> runtime_boxes;
}

BOOST_AUTO_TEST_CASE( test_box_map_copy_assignment )
{
  box_multimap<2, int2, int> boxes;
  boxes.insert(std::make_pair(zeros, 0));
  boxes.insert(std::make_pair(ones, 1));
  boxes.insert(std::make_pair(twos, 2));
  box_multimap<2, int2, int> copy(boxes);
  BOOST_CHECK_EQUAL(boxes.size(), copy.size());
  BOOST_CHECK(*boxes.begin() == *copy.begin());
  boxes = copy;
  BOOST_CHECK_EQUAL(boxes.size(), copy.size());
  BOOST_CHECK(*boxes.begin() == *copy.begin());
}

BOOST_AUTO_TEST_CASE( test_zero_box_map_copy_assignment )
{
  box_multimap<0, int2, int> boxes;
  boxes.insert(std::make_pair(zeros, 0));
  boxes.insert(std::make_pair(ones, 1));
  boxes.insert(std::make_pair(twos, 2));
  box_multimap<0, int2, int> copy(boxes);
  BOOST_CHECK_EQUAL(boxes.size(), copy.size());
  BOOST_CHECK(*boxes.begin() == *copy.begin());
  boxes = copy;
  BOOST_CHECK_EQUAL(boxes.size(), copy.size());
  BOOST_CHECK(*boxes.begin() == *copy.begin());
}


/*
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);
}