#pragma once

// [grid3d.h]: 3D primary virtual grid
//
// -------------------------------------------------------------------------------------------- //
// TO DO:
// 

#include "nse-sys.h"
#include "mpi-com3d.h"
#include "cart-sys3d.h"
#include "vecmath.h"

#include "grid-id.h"

#ifndef EXCLUDE_GPU_BRANCH
#include "grid.cuh"
#endif

// use hand-coded openmp array reduction 
//   in -xy averaging without critical sections
//   * - use on Intel Xeon Phi
#define USE_OMP_PAR_REDUCE_IN_AVG_XY

#define USE_GRID3D_BINARY_LOCATE


namespace nse
{
	namespace nse_const3d 
	{
		enum nodeType {
			nodeU = 0, nodeV = 1, nodeW = 2, nodeC = 3,
			nodeUV = 4, nodeUW = 5, nodeVW = 6, nodeUVW = 7
		};
	}

	// --- grid node after averaging
	nse_const3d::nodeType avg_node(
		const nse_const3d::nodeType node,
		const nse_const3d::axisType axis);
	// ------------------------------------------------------------------------------------------------ //


	// * 3D primart grid: Grid3d< T > [ T = float, double ] * //
	// =======================================================================
	template< typename T, memType mem = memCPU >
	class Grid3d
	{
	public:
		Grid3d();
		Grid3d(const Grid3d& grid);
		virtual ~Grid3d();

		// grid parameters by axis
		int dim_size(const nse_const3d::axisType axis) const;
		int mpi_dim_size(const nse_const3d::axisType axis) const;

		int ghost_region_size(	// single layer //
			const nse_const3d::axisType axis) const;	


		// MPI global cell index [== -1 - on failure]
		virtual int mpi_locate_x(const T x) const = 0;
		virtual int mpi_locate_y(const T y) const = 0;
		virtual int mpi_locate_z(const T z) const = 0;

		// local cell index [== -1 - on failure]
		//  - only one MPI process returns >=0 for (x,y,z) calls
		virtual int locate_x(const T x) const;
		virtual int locate_y(const T y) const;
		virtual int locate_z(const T z) const;

		// local local cell index on single MPI process [== -1 - on failure]
		//  - searching in segment [iexp - iwidth, iexp + iwdith]
		virtual int locate_local_x(const T x, const int iexp, const int iwidth) const;
		virtual int locate_local_y(const T y, const int jexp, const int jwidth) const;
		virtual int locate_local_z(const T z, const int kexp, const int kwidth) const;
		//	* search for a list of coordinates
		virtual void locate_local_x(const T* _RESTRICT const x,
			int* _RESTRICT iexp, const int iwidth, const int n) const;
		virtual void locate_local_y(const T* _RESTRICT const y,
			int* _RESTRICT jexp, const int jwidth, const int n) const;
		virtual void locate_local_z(const T* _RESTRICT const z,
			int* _RESTRICT kexp, const int kwidth, const int n) const;

		// (i,j,k) MPI-local coordinates [input - global index]
		//	- only one MPI process returns >= 0
		virtual int i_local_coord(const int i) const;
		virtual int j_local_coord(const int j) const;
		virtual int k_local_coord(const int k) const;

		// interpolation (local relative to (x,y,z) position in processor domain)
		virtual T c_interp(const T* X, const T x, const T y, const T z) const = 0;
		virtual T u_interp(const T* U, const T x, const T y, const T z) const = 0;
		virtual T v_interp(const T* V, const T x, const T y, const T z) const = 0;
		virtual T w_interp(const T* W, const T x, const T y, const T z) const = 0;

		// local interpolation on single MPI process 
		// [unsafe] - not checking if coordinates [(x,y,z),(i,j,k)] are correct
		virtual T u_interp_local(const T* _RESTRICT const U,
			const T x, const T y, const T z, const int i, const int j, const int k) const = 0;
		virtual T v_interp_local(const T* _RESTRICT const V,
			const T x, const T y, const T z, const int i, const int j, const int k) const = 0;
		virtual T w_interp_local(const T* _RESTRICT const W,
			const T x, const T y, const T z, const int i, const int j, const int k) const = 0;

		virtual void u_interp_local(T* _RESTRICT uinterp, const T* _RESTRICT const U,
			const T* _RESTRICT const x, const T* _RESTRICT const y, const T* _RESTRICT const z,
			const int* _RESTRICT const i, const int* _RESTRICT const j, const int* _RESTRICT const k, const int n) const = 0;
		virtual void v_interp_local(T* _RESTRICT vinterp, const T* _RESTRICT const V,
			const T* _RESTRICT const x, const T* _RESTRICT const y, const T* _RESTRICT const z,
			const int* _RESTRICT const i, const int* _RESTRICT const j, const int* _RESTRICT const k, const int n) const = 0;
		virtual void w_interp_local(T* _RESTRICT winterp, const T* _RESTRICT const W,
			const T* _RESTRICT const x, const T* _RESTRICT const y, const T* _RESTRICT const z,
			const int* _RESTRICT const i, const int* _RESTRICT const j, const int* _RESTRICT const k, const int n) const = 0;

		// interpolation (global to (x,y,z) point position)
		virtual T mpi_c_interp(const T* X, const T x, const T y, const T z) const;
		virtual T mpi_u_interp(const T* U, const T x, const T y, const T z) const;
		virtual T mpi_v_interp(const T* V, const T x, const T y, const T z) const;
		virtual T mpi_w_interp(const T* W, const T x, const T y, const T z) const;

		// slicing
		// -xy slice, [C,U,V,W] -> [C,U,V,C]
		void c_slice_at_z(T* Pxy, const T* X, const T z) const;
		void u_slice_at_z(T* Pxy, const T* U, const T z) const;
		void v_slice_at_z(T* Pxy, const T* V, const T z) const;
		void w_slice_at_z(T* Pxy, const T* W, const T z) const;
		// -xz slice, [C,U,V,W] -> [C,U,C,W]
		void c_slice_at_y(T* Pxz, const T* X, const T y) const;
		void u_slice_at_y(T* Pxz, const T* U, const T y) const;
		void v_slice_at_y(T* Pxz, const T* V, const T y) const;
		void w_slice_at_y(T* Pxz, const T* W, const T y) const;
		// -yz slice [C,U,V,W] -> [C,C,V,W]
		void c_slice_at_x(T* Pyz, const T* X, const T x) const;
		void u_slice_at_x(T* Pyz, const T* U, const T x) const;
		void v_slice_at_x(T* Pyz, const T* V, const T x) const;
		void w_slice_at_x(T* Pyz, const T* W, const T x) const;

		// MPI gather-scatter by axis //
		template< memType memOUT = memCPU, memType memIN = memCPU, typename Tin >
		void mpi_gather(Tin* _RESTRICT out, const Tin* _RESTRICT in,
			const int host, const nse_const3d::axisType axis) const;
		template< memType memOUT = memCPU, memType memIN = memCPU, typename Tin >
		void mpi_scatter(Tin* _RESTRICT out, const Tin* _RESTRICT in,
			const int host, const nse_const3d::axisType axis) const;

		// MPI gather coordinates //
		template< memType memOUT = memCPU >
		void mpi_gather_center_coord(T* _RESTRICT out,
			const int host, const nse_const3d::axisType axis) const;
		template< memType memOUT = memCPU >
		void mpi_gather_edge_coord(T* _RESTRICT out,
			const int host, const nse_const3d::axisType axis) const;

		template< memType memOUT = memCPU >
		void mpi_gather_center_coord(
			T* _RESTRICT xout, T* _RESTRICT yout, T* _RESTRICT zout, const int host) const;
		template< memType memOUT = memCPU >
		void mpi_gather_edge_coord(
			T* _RESTRICT xout, T* _RESTRICT yout, T* _RESTRICT zout, const int host) const;


		// grid re-interpolation out(current grid), in(input grid)
		virtual void grid_reinterp(T* Xout, const T* Xin,				// local in array //
			const nse_const3d::nodeType node, const GridId< T >& id) const = 0;

		// GridId on 3D grids //
		virtual void set_id(GridId< T >& id) const;
		virtual bool check_id(const GridId< T >& id) const;

		bool check_id_dims(const GridId< T >& id) const;


		virtual void set_id(GridId< T >& id, const nse_const3d::axisType axis) const;
		virtual bool check_id(const GridId< T >& id, const nse_const3d::axisType axis) const;

		bool check_id_dims(const GridId< T >& id, const nse_const3d::axisType axis) const;

	public:

		mpiCom3d mpi_com;

		int size, nx, ny, nz, nyz;
		int mpi_nx, mpi_ny, mpi_nz,
			mpi_nxy, mpi_nxz, mpi_nyz, mpi_size;

		int gcx, gcy, gcz;

		T x, y, z;
		T length, width, height;

		T mpi_x, mpi_y, mpi_z;
		T mpi_length, mpi_width, mpi_height;

		T *px, *py, *pz;		// cell-center coordinates //
		T *ex, *ey, *ez;		// cell-edge coordinates //
	};
}

// Implementation [misc]
// -------------------------------------------------------------------------------------------- //
inline
nse::nse_const3d::nodeType nse::avg_node(const nse_const3d::nodeType node,
	const nse_const3d::axisType axis)
{
	if (axis == nse_const3d::axisX) {
		if ((node == nse_const3d::nodeU) || (node == nse_const3d::nodeUV) ||
			(node == nse_const3d::nodeUW) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeU;

		return nse_const3d::nodeC;
	}
	if (axis == nse_const3d::axisY) {
		if ((node == nse_const3d::nodeV) || (node == nse_const3d::nodeUV) ||
			(node == nse_const3d::nodeVW) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeV;

		return nse_const3d::nodeC;
	}
	if (axis == nse_const3d::axisZ) {
		if ((node == nse_const3d::nodeW) || (node == nse_const3d::nodeUW) ||
			(node == nse_const3d::nodeVW) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeW;

		return nse_const3d::nodeC;
	}

	if (axis == nse_const3d::axisXY) {
		if ((node == nse_const3d::nodeV) || (node == nse_const3d::nodeVW))
			return nse_const3d::nodeV;
		if ((node == nse_const3d::nodeU) || (node == nse_const3d::nodeUW))
			return nse_const3d::nodeU;
		if ((node == nse_const3d::nodeUV) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeUV;

		return nse_const3d::nodeC;
	}
	if (axis == nse_const3d::axisXZ) {
		if ((node == nse_const3d::nodeW) || (node == nse_const3d::nodeVW))
			return nse_const3d::nodeW;
		if ((node == nse_const3d::nodeU) || (node == nse_const3d::nodeUV))
			return nse_const3d::nodeU;
		if ((node == nse_const3d::nodeUW) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeUW;

		return nse_const3d::nodeC;
	}
	if (axis == nse_const3d::axisYZ) {
		if ((node == nse_const3d::nodeW) || (node == nse_const3d::nodeUW))
			return nse_const3d::nodeW;
		if ((node == nse_const3d::nodeV) || (node == nse_const3d::nodeUV))
			return nse_const3d::nodeV;
		if ((node == nse_const3d::nodeVW) || (node == nse_const3d::nodeUVW))
			return nse_const3d::nodeVW;

		return nse_const3d::nodeC;
	}

	// axis == nse_const3d::axisXYZ
	return node;
}
// -------------------------------------------------------------------------------------------- //

// Implementation, Grid3d:
// -------------------------------------------------------------------------------------------- //
namespace nse
{
	template< typename T, memType mem >
	Grid3d< T, mem > ::Grid3d(
	) : mpi_com(),
		size(0), nx(0), ny(0), nz(0), nyz(0),
		mpi_size(0), mpi_nx(0), mpi_ny(0), mpi_nz(0),
		mpi_nxy(0), mpi_nxz(0), mpi_nyz(0),
		gcx(0), gcy(0), gcz(0),

		x((T)0), y((T)0), z((T)0),
		length((T)0), width((T)0), height((T)0),
		mpi_x((T)0), mpi_y((T)0), mpi_z((T)0),
		mpi_length((T)0), mpi_width((T)0), mpi_height((T)0)
	{
	}

	template< typename T, memType mem >
	Grid3d< T, mem > ::Grid3d(
		const Grid3d< T, mem >& grid)
		: mpi_com(grid.mpi_com),
		size(grid.size), nx(grid.nx), ny(grid.ny), nz(grid.nz), nyz(grid.nyz),
		mpi_size(grid.size), mpi_nx(grid.mpi_nx), mpi_ny(grid.mpi_ny), mpi_nz(grid.mpi_nz),
		mpi_nxy(grid.mpi_nxy), mpi_nxz(grid.mpi_nxz), mpi_nyz(grid.mpi_nyz),
		gcx(grid.gcx), gcy(grid.gcy), gcz(grid.gcz),

		x(grid.x), y(grid.y), z(grid.z),
		mpi_x(grid.mpi_x), mpi_y(grid.mpi_y), mpi_z(grid.mpi_z),

		length(grid.length), width(grid.width), height(grid.mpi_height),
		mpi_length(grid.mpi_length), mpi_width(grid.mpi_width), mpi_height(grid.mpi_height)
	{
		if (size > 0) {
			allocate<mem>(&px, nx); mcopy<mem, mem>(px, grid.px, nx);
			allocate<mem>(&py, ny); mcopy<mem, mem>(py, grid.py, ny);
			allocate<mem>(&pz, nz); mcopy<mem, mem>(pz, grid.pz, nz);

			allocate<mem>(&ex, nx); mcopy<mem, mem>(ex, grid.ex, nx);
			allocate<mem>(&ey, ny); mcopy<mem, mem>(ey, grid.ey, ny);
			allocate<mem>(&ez, nz); mcopy<mem, mem>(ez, grid.ez, nz);
		}
	}

	template< typename T, memType mem >
	Grid3d< T, mem > :: ~Grid3d(
	)
	{
		if (size > 0) {

			deallocate<mem>(px); deallocate<mem>(py); deallocate<mem>(pz);
			deallocate<mem>(ex); deallocate<mem>(ey); deallocate<mem>(ez);
		}

	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::dim_size(const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX: return nx;
		case nse_const3d::axisY: return ny;
		case nse_const3d::axisZ: return nz;
		case nse_const3d::axisXY: return nx * ny;
		case nse_const3d::axisXZ: return nx * nz;
		case nse_const3d::axisYZ: return nyz;
		case nse_const3d::axisXYZ: return size;
		default: return -1;
		}
	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::mpi_dim_size(const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX: return mpi_nx;
		case nse_const3d::axisY: return mpi_ny;
		case nse_const3d::axisZ: return mpi_nz;
		case nse_const3d::axisXY: return mpi_nxy;
		case nse_const3d::axisXZ: return mpi_nxz;
		case nse_const3d::axisYZ: return mpi_nyz;
		case nse_const3d::axisXYZ: return mpi_size;
		default: return -1;
		}
	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::ghost_region_size(const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX: return gcx;
		case nse_const3d::axisY: return gcy;
		case nse_const3d::axisZ: return gcz;
		case nse_const3d::axisXY: return gcx * gcy;
		case nse_const3d::axisXZ: return gcx * gcz;
		case nse_const3d::axisYZ: return gcy * gcz;
		case nse_const3d::axisXYZ: return gcx * gcy * gcz;
		default: return -1;
		}
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_x(const T _x) const
	{
#ifndef EXCLUDE_GPU_BRANCH
		if (mem == memGPU) {
			if (mpi_com.rank_x == 0)
				return nse_gpu::locate(_x, ex, nx, gcx, 0);
			else
				return nse_gpu::locate(_x, ex, nx, gcx, 1);
		}
		else
#endif
		{	// memCPU //

#ifdef USE_GRID3D_BINARY_LOCATE
			const int min_binary_size = 16;
#endif

			int i;
			int ibeg = gcx, iend = nx - gcx - 1;


			if (mpi_com.rank_x == 0)
			{
#ifdef USE_GRID3D_BINARY_LOCATE	
				while (iend - ibeg >= min_binary_size) {
					i = (ibeg + iend) / 2;
					if (_x < ex[i]) {
						iend = i - 1; continue;
					}
					if (_x > ex[i + 1]) {
						ibeg = i + 1; continue;
					}

					return i;
				}
#endif
				for (i = ibeg; i <= iend; i++)
					if ((_x >= ex[i]) && (_x <= ex[i + 1])) { return i; }
			}
			else
			{
#ifdef USE_GRID3D_BINARY_LOCATE
				while (iend - ibeg >= min_binary_size) {
					i = (ibeg + iend) / 2;
					if (_x <= ex[i]) {
						iend = i - 1; continue;
					}
					if (_x > ex[i + 1]) {
						ibeg = i + 1; continue;
					}

					return i;
				}
#endif
				for (i = ibeg; i <= iend; i++)
					if ((_x > ex[i]) && (_x <= ex[i + 1])) { return i; }
			}

			return -1;
		}
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_y(const T _y) const
	{
#ifndef EXCLUDE_GPU_BRANCH
		if (mem == memGPU) {
			if (mpi_com.rank_y == 0)
				return nse_gpu::locate(_y, ey, ny, gcy, 0);
			else
				return nse_gpu::locate(_y, ey, ny, gcy, 1);
		}
		else
#endif
		{	// memCPU //

#ifdef USE_GRID3D_BINARY_LOCATE
			const int min_binary_size = 16;
#endif

			int j;
			int jbeg = gcy, jend = ny - gcy - 1;


			if (mpi_com.rank_y == 0) {
#ifdef USE_GRID3D_BINARY_LOCATE

				while (jend - jbeg >= min_binary_size) {
					j = (jbeg + jend) / 2;
					if (_y < ey[j]) {
						jend = j - 1; continue;
					}
					if (_y > ey[j + 1]) {
						jbeg = j + 1; continue;
					}

					return j;
				}
#endif
				for (j = jbeg; j <= jend; j++)
					if ((_y >= ey[j]) && (_y <= ey[j + 1])) { return j; }
			}
			else
			{
#ifdef USE_GRID3D_BINARY_LOCATE
				while (jend - jbeg >= min_binary_size) {
					j = (jbeg + jend) / 2;
					if (_y <= ey[j]) {
						jend = j - 1; continue;
					}
					if (_y > ey[j + 1]) {
						jbeg = j + 1; continue;
					}

					return j;
				}
#endif
				for (j = jbeg; j <= jend; j++)
					if ((_y > ey[j]) && (_y <= ey[j + 1])) { return j; }
			}

			return -1;
		}
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_z(const T _z) const
	{
#ifndef EXCLUDE_GPU_BRANCH
		if (mem == memGPU) {
			if (mpi_com.rank_z == 0)
				return nse_gpu::locate(_z, ez, nz, gcz, 0);
			else
				return nse_gpu::locate(_z, ez, nz, gcz, 1);
		}
		else
#endif
		{	// memCPU //

#ifdef USE_GRID3D_BINARY_LOCATE
			const int min_binary_size = 16;
#endif

			int k;
			int kbeg = gcz, kend = nz - gcz - 1;


			if (mpi_com.rank_z == 0) {
#ifdef USE_GRID3D_BINARY_LOCATE
				while (kend - kbeg >= min_binary_size) {
					k = (kbeg + kend) / 2;
					if (_z < ez[k]) {
						kend = k - 1; continue;
					}
					if (_z > ez[k + 1]) {
						kbeg = k + 1; continue;
					}

					return k;
				}
#endif
				for (k = kbeg; k <= kend; k++)
					if ((_z >= ez[k]) && (_z <= ez[k + 1])) { return k; }
			}
			else
			{
#ifdef USE_GRID3D_BINARY_LOCATE
				while (kend - kbeg >= min_binary_size) {
					k = (kbeg + kend) / 2;
					if (_z <= ez[k]) {
						kend = k - 1; continue;
					}
					if (_z > ez[k + 1]) {
						kbeg = k + 1; continue;
					}

					return k;
				}
#endif
				for (k = kbeg; k <= kend; k++)
					if ((_z > ez[k]) && (_z <= ez[k + 1])) { return k; }
			}

			return -1;
		}
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_local_x(
		const T _x, const int iexp, const int iwidth) const
	{
#ifdef USE_GRID3D_BINARY_LOCATE
		const int min_binary_size = 16;
#endif

		int i;
		int ibeg = max(iexp - iwidth, gcx);
		int iend = min(iexp + iwidth, nx - gcx - 1);

		if (mpi_com.rank_x == 0) {

#ifdef USE_GRID3D_BINARY_LOCATE
			while (iend - ibeg >= min_binary_size) {
				i = (ibeg + iend) / 2;
				if (_x < ex[i]) {
					iend = i - 1; continue;
				}
				if (_x > ex[i + 1]) {
					ibeg = i + 1; continue;
				}

				return i;
			}
#endif
			for (i = ibeg; i <= iend; i++)
				if ((_x >= ex[i]) && (_x <= ex[i + 1])) { return i; }
		}
		else
		{
#ifdef USE_GRID3D_BINARY_LOCATE
			while (iend - ibeg >= min_binary_size) {
				i = (ibeg + iend) / 2;
				if (_x <= ex[i]) {
					iend = i - 1; continue;
				}
				if (_x > ex[i + 1]) {
					ibeg = i + 1; continue;
				}

				return i;
			}
#endif
			for (i = ibeg; i <= iend; i++)
				if ((_x > ex[i]) && (_x <= ex[i + 1])) { return i; }
		}

		return -1;
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_local_y(
		const T _y, const int jexp, const int jwidth) const
	{
#ifdef USE_GRID3D_BINARY_LOCATE
		const int min_binary_size = 16;
#endif

		int j;
		int jbeg = max(jexp - jwidth, gcy);
		int jend = min(jexp + jwidth, ny - gcy - 1);

		if (mpi_com.rank_y == 0) {
#ifdef USE_GRID3D_BINARY_LOCATE
			while (jend - jbeg >= min_binary_size) {
				j = (jbeg + jend) / 2;
				if (_y < ey[j]) {
					jend = j - 1; continue;
				}
				if (_y > ey[j + 1]) {
					jbeg = j + 1; continue;
				}

				return j;
			}
#endif
			for (j = jbeg; j <= jend; j++)
				if ((_y >= ey[j]) && (_y <= ey[j + 1])) { return j; }
		}
		else
		{
#ifdef USE_GRID3D_BINARY_LOCATE
			while (jend - jbeg >= min_binary_size) {
				j = (jbeg + jend) / 2;
				if (_y <= ey[j]) {
					jend = j - 1; continue;
				}
				if (_y > ey[j + 1]) {
					jbeg = j + 1; continue;
				}

				return j;
			}
#endif
			for (j = jbeg; j <= jend; j++)
				if ((_y > ey[j]) && (_y <= ey[j + 1])) { return j; }
		}

		return -1;
	}

	template< typename T, memType mem >
	inline int Grid3d< T, mem > ::locate_local_z(
		const T _z, const int kexp, const int kwidth) const
	{
#ifdef USE_GRID3D_BINARY_LOCATE
		const int min_binary_size = 16;
#endif

		int k;
		int kbeg = max(kexp - kwidth, gcz);
		int kend = min(kexp + kwidth, nz - gcz - 1);

		if (mpi_com.rank_z == 0) {
#ifdef USE_GRID3D_BINARY_LOCATE
			while (kend - kbeg >= min_binary_size) {
				k = (kbeg + kend) / 2;
				if (_z < ez[k]) {
					kend = k - 1; continue;
				}
				if (_z > ez[k + 1]) {
					kbeg = k + 1; continue;
				}

				return k;
			}
#endif
			for (k = kbeg; k <= kend; k++)
				if ((_z >= ez[k]) && (_z <= ez[k + 1])) { return k; }
		}
		else
		{
#ifdef USE_GRID3D_BINARY_LOCATE
			while (kend - kbeg >= min_binary_size) {
				k = (kbeg + kend) / 2;
				if (_z <= ez[k]) {
					kend = k - 1; continue;
				}
				if (_z > ez[k + 1]) {
					kbeg = k + 1; continue;
				}

				return k;
			}
#endif
			for (k = kbeg; k <= kend; k++)
				if ((_z > ez[k]) && (_z <= ez[k + 1])) { return k; }
		}

		return -1;
	}

	template< typename T, memType mem >
	inline void Grid3d< T, mem > ::locate_local_x(const T* _RESTRICT const _x,
		int* _RESTRICT ipos, const int iwidth, const int n) const
	{
		int m, i;
		int ibeg, iend, idx;

		if (mpi_com.rank_x == 0) {
#pragma omp parallel for private(m, i, ibeg, iend, idx) shared(ipos)
			for (m = 0; m < n; m++) {

				ibeg = max(ipos[m] - iwidth, gcx);
				iend = min(ipos[m] + iwidth, nx - gcx - 1);

				idx = -1;
				for (i = ibeg; i <= iend; i++)
					if ((_x[m] >= ex[i]) && (_x[m] <= ex[i + 1])) { idx = i; break; }
				ipos[m] = idx;
			}
		}
		else
		{
#pragma omp parallel for private(m, i, ibeg, iend, idx) shared(ipos)
			for (m = 0; m < n; m++) {

				ibeg = max(ipos[m] - iwidth, gcx);
				iend = min(ipos[m] + iwidth, nx - gcx - 1);

				idx = -1;
				for (i = ibeg; i <= iend; i++)
					if ((_x[m] > ex[i]) && (_x[m] <= ex[i + 1])) { idx = i; break; }
				ipos[m] = idx;
			}
		}
	}

	template< typename T, memType mem >
	inline void Grid3d< T, mem > ::locate_local_y(const T* _RESTRICT const _y,
		int* _RESTRICT jpos, const int jwidth, const int n) const
	{
		int m, j;
		int jbeg, jend, idx;

		if (mpi_com.rank_y == 0) {
#pragma omp parallel for private(m, j, jbeg, jend, idx) shared(jpos)
			for (m = 0; m < n; m++) {

				jbeg = max(jpos[m] - jwidth, gcy);
				jend = min(jpos[m] + jwidth, ny - gcy - 1);

				idx = -1;
				for (j = jbeg; j <= jend; j++)
					if ((_y[m] >= ey[j]) && (_y[m] <= ey[j + 1])) { idx = j; break; }
				jpos[m] = idx;
			}
		}
		else
		{
#pragma omp parallel for private(m, j, jbeg, jend, idx) shared(jpos)
			for (m = 0; m < n; m++) {

				jbeg = max(jpos[m] - jwidth, gcy);
				jend = min(jpos[m] + jwidth, ny - gcy - 1);

				idx = -1;
				for (j = jbeg; j <= jend; j++)
					if ((_y[m] > ey[j]) && (_y[m] <= ey[j + 1])) { idx = j; break; }
				jpos[m] = idx;
			}
		}
	}

	template< typename T, memType mem >
	inline void Grid3d< T, mem > ::locate_local_z(const T* _RESTRICT const _z,
		int* _RESTRICT kpos, const int kwidth, const int n) const
	{
		int m, k;
		int kbeg, kend, idx;

		if (mpi_com.rank_z == 0) {
#pragma omp parallel for private(m, k, kbeg, kend, idx) shared(kpos)
			for (m = 0; m < n; m++) {

				kbeg = max(kpos[m] - kwidth, gcz);
				kend = min(kpos[m] + kwidth, nz - gcz - 1);

				idx = -1;
				for (k = kbeg; k <= kend; k++)
					if ((_z[m] >= ez[k]) && (_z[m] <= ez[k + 1])) { idx = k; break; }
				kpos[m] = idx;
			}
		}
		else
		{
#pragma omp parallel for private(m, k, kbeg, kend, idx) shared(kpos)
			for (m = 0; m < n; m++) {

				kbeg = max(kpos[m] - kwidth, gcz);
				kend = min(kpos[m] + kwidth, nz - gcz - 1);

				idx = -1;
				for (k = kbeg; k <= kend; k++)
					if ((_z[m] > ez[k]) && (_z[m] <= ez[k + 1])) { idx = k; break; }
				kpos[m] = idx;
			}
		}
	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::i_local_coord(const int i) const
	{
		if ((i < 0) & (i > mpi_nx - 1)) return -1;

		const int shmx = (mpi_com.rank_x == 0) ? gcx : 0;
		const int shpx = (mpi_com.rank_x == mpi_com.size_x - 1) ? gcx : 0;

		int ip = i - par_local_offset(mpi_nx, gcx,
			mpi_com.rank_x, mpi_com.size_x);

		return ((ip >= gcx - shmx) && (ip < nx - gcx + shpx)) ? ip : -1;
	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::j_local_coord(const int j) const
	{
		if ((j < 0) && (j > mpi_ny - 1)) return -1;

		const int shmy = (mpi_com.rank_y == 0) ? gcy : 0;
		const int shpy = (mpi_com.rank_y == mpi_com.size_y - 1) ? gcy : 0;

		int jp = j - par_local_offset(mpi_ny, gcy,
			mpi_com.rank_y, mpi_com.size_y);

		return ((jp >= gcy - shmy) && (jp < ny - gcy + shpy)) ? jp : -1;
	}

	template< typename T, memType mem >
	int Grid3d< T, mem > ::k_local_coord(const int k) const
	{
		if ((k < 0) && (k > mpi_nz - 1)) return -1;

		const int shmz = (mpi_com.rank_z == 0) ? gcz : 0;
		const int shpz = (mpi_com.rank_z == mpi_com.size_z - 1) ? gcz : 0;

		int kp = k - par_local_offset(mpi_nz, gcz,
			mpi_com.rank_z, mpi_com.size_z);

		return ((kp >= gcz - shmz) && (kp < nz - gcz + shpz)) ? kp : -1;
	}

	template< typename T, memType mem >
	T Grid3d< T, mem > ::mpi_c_interp(const T* X, const T _px, const T _py, const T _pz) const
	{
		return mpi_allreduce(
			c_interp(X, _px, _py, _pz), MPI_SUM, mpi_com.comm);
	}

	template< typename T, memType mem >
	T Grid3d< T, mem > ::mpi_u_interp(const T* U, const T _px, const T _py, const T _pz) const
	{
		return mpi_allreduce(
			u_interp(U, _px, _py, _pz), MPI_SUM, mpi_com.comm);
	}

	template< typename T, memType mem >
	T Grid3d< T, mem > ::mpi_v_interp(const T* V, const T _px, const T _py, const T _pz) const
	{
		return mpi_allreduce(
			v_interp(V, _px, _py, _pz), MPI_SUM, mpi_com.comm);
	}

	template< typename T, memType mem >
	T Grid3d< T, mem > ::mpi_w_interp(const T* W, const T _px, const T _py, const T _pz) const
	{
		return mpi_allreduce(
			w_interp(W, _px, _py, _pz), MPI_SUM, mpi_com.comm);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::c_slice_at_z(T* Pxy, const T* X, const T _pz) const
	{
		int i, j, k = locate_z(_pz);
		int index, host_rank = -1;

		null(Pxy, nx * ny);
		if ((k >= gcz) && (k < nz - gcz)) {

			const int kpos = (_pz < pz[k]) ? k : k + 1;
			const T alpha = (_pz - pz[kpos - 1]) / (pz[kpos] - pz[kpos - 1]);

#pragma omp parallel for private( i, j, index ) shared( Pxy )
			for (i = gcx; i < nx - gcx; i++) {
				for (j = gcy; j < ny - gcy; j++) {
					index = i * nyz + j * nz + kpos;

					Pxy[i * ny + j] = ((T)1.0 - alpha) * X[index - 1] + alpha * X[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_z, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_z);
		if (host_rank >= 0)
			mpi_broadcast(Pxy, nx * ny, host_rank, mpi_com.comm_z);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::u_slice_at_z(T* Pxy, const T* U, const T _pz) const
	{
		int i, j, k = locate_z(_pz);
		int index, host_rank = -1;

		null(Pxy, nx * ny);
		if ((k >= gcz) && (k < nz - gcz)) {

			const int kpos = (_pz < pz[k]) ? k : k + 1;
			const T alpha = (_pz - pz[kpos - 1]) / (pz[kpos] - pz[kpos - 1]);

#pragma omp parallel for private( i, j, index ) shared( Pxy )
			for (i = gcx; i < nx - gcx + 1; i++) {
				for (j = gcy; j < ny - gcy; j++) {
					index = i * nyz + j * nz + kpos;

					Pxy[i * ny + j] = ((T)1.0 - alpha) * U[index - 1] + alpha * U[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_z, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_z);
		if (host_rank >= 0)
			mpi_broadcast(Pxy, nx * ny, host_rank, mpi_com.comm_z);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::v_slice_at_z(T* Pxy, const T* V, const T _pz) const
	{
		int i, j, k = locate_z(_pz);
		int index, host_rank = -1;

		null(Pxy, nx * ny);
		if ((k >= gcz) && (k < nz - gcz)) {

			const int kpos = (_pz < pz[k]) ? k : k + 1;
			const T alpha = (_pz - pz[kpos - 1]) / (pz[kpos] - pz[kpos - 1]);

#pragma omp parallel for private( i, j, index ) shared( Pxy )
			for (i = gcx; i < nx - gcx; i++) {
				for (j = gcy; j < ny - gcy + 1; j++) {
					index = i * nyz + j * nz + kpos;

					Pxy[i * ny + j] = ((T)1.0 - alpha) * V[index - 1] + alpha * V[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_z, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_z);
		if (host_rank >= 0)
			mpi_broadcast(Pxy, nx * ny, host_rank, mpi_com.comm_z);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::w_slice_at_z(T* Pxy, const T* W, const T _pz) const
	{
		int i, j, k = locate_z(_pz);
		int index, host_rank = -1;

		null(Pxy, nx * ny);
		if ((k >= gcz) && (k < nz - gcz))
		{
			const int kpos = k + 1;
			const T alpha = (_pz - ez[kpos - 1]) / (ez[kpos] - ez[kpos - 1]);

#pragma omp parallel for private( i, j, index ) shared( Pxy )
			for (i = gcx; i < nx - gcx; i++) {
				for (j = gcy; j < ny - gcy; j++) {
					index = i * nyz + j * nz + kpos;

					Pxy[i * ny + j] = ((T)1.0 - alpha) * W[index - 1] + alpha * W[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_z, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_z);
		if (host_rank >= 0)
			mpi_broadcast(Pxy, nx * ny, host_rank, mpi_com.comm_z);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::c_slice_at_y(T* Pxz, const T* X, const T _py) const
	{
		int i, j = locate_y(_py), k;
		int index, host_rank = -1;

		null(Pxz, nx * nz);
		if ((j >= gcy) && (j < ny - gcy)) {

			const int jpos = (_py < py[j]) ? j : j + 1;
			const T alpha = (_py - py[jpos - 1]) / (py[jpos] - py[jpos - 1]);

#pragma omp parallel for private( i, k, index ) shared( Pxz )
			for (i = gcx; i < nx - gcx; i++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = i * nyz + jpos * nz + k;

					Pxz[i * nz + k] = ((T)1.0 - alpha) * X[index - nz] + alpha * X[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_y, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_y);
		if (host_rank >= 0)
			mpi_broadcast(Pxz, nx * nz, host_rank, mpi_com.comm_y);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::u_slice_at_y(T* Pxz, const T* U, const T _py) const
	{
		int i, j = locate_y(_py), k;
		int index, host_rank = -1;

		null(Pxz, nx * nz);
		if ((j >= gcy) && (j < ny - gcy)) {

			const int jpos = (_py < py[j]) ? j : j + 1;
			const T alpha = (_py - py[jpos - 1]) / (py[jpos] - py[jpos - 1]);

#pragma omp parallel for private( i, k, index ) shared( Pxz )
			for (i = gcx; i < nx - gcx + 1; i++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = i * nyz + jpos * nz + k;

					Pxz[i * nz + k] = ((T)1.0 - alpha) * U[index - nz] + alpha * U[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_y, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_y);
		if (host_rank >= 0)
			mpi_broadcast(Pxz, nx * nz, host_rank, mpi_com.comm_y);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::v_slice_at_y(T* Pxz, const T* V, const T _py) const
	{
		int i, j = locate_y(_py), k;
		int index, host_rank = -1;

		null(Pxz, nx * nz);
		if ((j >= gcy) && (j < ny - gcy)) {

			const int jpos = j + 1;
			const T alpha = (_py - ey[jpos - 1]) / (ey[jpos] - ey[jpos - 1]);

#pragma omp parallel for private( i, k, index ) shared( Pxz )
			for (i = gcx; i < nx - gcx; i++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = i * nyz + jpos * nz + k;

					Pxz[i * nz + k] = ((T)1.0 - alpha) * V[index - nz] + alpha * V[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_y, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_y);
		if (host_rank >= 0)
			mpi_broadcast(Pxz, nx * nz, host_rank, mpi_com.comm_y);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::w_slice_at_y(T* Pxz, const T* W, const T _py) const
	{
		int i, j = locate_y(_py), k;
		int index, host_rank = -1;

		null(Pxz, nx * nz);
		if ((j >= gcy) && (j < ny - gcy)) {

			const int jpos = (_py < py[j]) ? j : j + 1;
			const T alpha = (_py - py[jpos - 1]) / (py[jpos] - py[jpos - 1]);

#pragma omp parallel for private( i, k, index ) shared( Pxz )
			for (i = gcx; i < nx - gcx; i++) {
				for (k = gcz; k < nz - gcz + 1; k++) {
					index = i * nyz + jpos * nz + k;

					Pxz[i * nz + k] = ((T)1.0 - alpha) * W[index - nz] + alpha * W[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_y, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_y);
		if (host_rank >= 0)
			mpi_broadcast(Pxz, nx * nz, host_rank, mpi_com.comm_y);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::c_slice_at_x(T* Pyz, const T* X, const T _px) const
	{
		int i = locate_x(_px), j, k;
		int index, host_rank = -1;

		null(Pyz, ny * nz);
		if ((i >= gcx) && (i < nx - gcx)) {

			const int ipos = (_px < px[i]) ? i : i + 1;
			const T alpha = (_px - px[ipos - 1]) / (px[ipos] - px[ipos - 1]);

#pragma omp parallel for private( j, k, index ) shared( Pyz )
			for (j = gcy; j < ny - gcy; j++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = ipos * nyz + j * nz + k;

					Pyz[j * nz + k] = ((T)1.0 - alpha) * X[index - nyz] + alpha * X[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_x, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_x);
		if (host_rank >= 0)
			mpi_broadcast(Pyz, ny * nz, host_rank, mpi_com.comm_x);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::u_slice_at_x(T* Pyz, const T* U, const T _px) const
	{
		int i = locate_x(_px), j, k;
		int index, host_rank = -1;

		null(Pyz, ny * nz);
		if ((i >= gcx) && (i < nx - gcx)) {

			const int ipos = i + 1;
			const T alpha = (_px - ex[ipos - 1]) / (ex[ipos] - ex[ipos - 1]);

#pragma omp parallel for private( j, k, index ) shared( Pyz )
			for (j = gcy; j < ny - gcy; j++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = ipos * nyz + j * nz + k;

					Pyz[j * nz + k] = ((T)1.0 - alpha) * U[index - nyz] + alpha * U[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_x, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_x);
		if (host_rank >= 0)
			mpi_broadcast(Pyz, ny * nz, host_rank, mpi_com.comm_x);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::v_slice_at_x(T* Pyz, const T* V, const T _px) const
	{
		int i = locate_x(_px), j, k;
		int index, host_rank = -1;

		null(Pyz, ny * nz);
		if ((i >= gcx) && (i < nx - gcx)) {

			const int ipos = (_px < px[i]) ? i : i + 1;
			const T alpha = (_px - px[ipos - 1]) / (px[ipos] - px[ipos - 1]);

#pragma omp parallel for private( j, k, index ) shared( Pyz )
			for (j = gcy; j < ny - gcy + 1; j++) {
				for (k = gcz; k < nz - gcz; k++) {
					index = ipos * nyz + j * nz + k;

					Pyz[j * nz + k] = ((T)1.0 - alpha) * V[index - nyz] + alpha * V[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_x, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_x);
		if (host_rank >= 0)
			mpi_broadcast(Pyz, ny * nz, host_rank, mpi_com.comm_x);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::w_slice_at_x(T* Pyz, const T* W, const T _px) const
	{
		int i = locate_x(_px), j, k;
		int index, host_rank = -1;

		null(Pyz, ny * nz);
		if ((i >= gcx) && (i < nx - gcx)) {

			const int ipos = (_px < px[i]) ? i : i + 1;
			const T alpha = (_px - px[ipos - 1]) / (px[ipos] - px[ipos - 1]);

#pragma omp parallel for private( j, k, index ) shared( Pyz )
			for (j = gcy; j < ny - gcy; j++) {
				for (k = gcz; k < nz - gcz + 1; k++) {
					index = ipos * nyz + j * nz + k;

					Pyz[j * nz + k] = ((T)1.0 - alpha) * W[index - nyz] + alpha * W[index];
				}
			}

			MPI_Comm_rank(mpi_com.comm_x, &host_rank);
		}

		mpi_allreduce(&host_rank, MPI_MAX, mpi_com.comm_x);
		if (host_rank >= 0)
			mpi_broadcast(Pyz, ny * nz, host_rank, mpi_com.comm_x);
	}

	template< typename T, memType mem >
	template< memType memOUT, memType memIN, typename Tin >
	void Grid3d< T, mem >::mpi_gather(Tin* _RESTRICT out, const Tin* _RESTRICT in,
		const int host, const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX:
			mpi_com.gather_x<memOUT, memIN>(out, in, host, nx, gcx); break;
		case nse_const3d::axisY:
			mpi_com.gather_y<memOUT, memIN>(out, in, host, ny, gcy); break;
		case nse_const3d::axisZ:
			mpi_com.gather_z<memOUT, memIN>(out, in, host, nz, gcz); break;
		case nse_const3d::axisXY:
			mpi_com.gather_xy<memOUT, memIN>(out, in, host, nx, ny, gcx, gcy); break;
		case nse_const3d::axisXZ:
			mpi_com.gather_xz<memOUT, memIN>(out, in, host, nx, nz, gcx, gcz); break;
		case nse_const3d::axisYZ:
			mpi_com.gather_yz<memOUT, memIN>(out, in, host, ny, nz, gcy, gcz); break;
		case nse_const3d::axisXYZ:
			mpi_com.gather<memOUT, memIN>(out, in, host, nx, ny, nz, gcx, gcy, gcz); break;
		default: return;
		}
	}

	template< typename T, memType mem >
	template< memType memOUT, memType memIN, typename Tin >
	void Grid3d< T, mem >::mpi_scatter(Tin* _RESTRICT out, const Tin* _RESTRICT in,
		const int host, const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX:
			mpi_com.scatter_x<memOUT, memIN>(out, in, host, nx, gcx); break;
		case nse_const3d::axisY:
			mpi_com.scatter_y<memOUT, memIN>(out, in, host, ny, gcy); break;
		case nse_const3d::axisZ:
			mpi_com.scatter_z<memOUT, memIN>(out, in, host, nz, gcz); break;
		case nse_const3d::axisXY:
			mpi_com.scatter_xy<memOUT, memIN>(out, in, host, nx, ny, gcx, gcy); break;
		case nse_const3d::axisXZ:
			mpi_com.scatter_xz<memOUT, memIN>(out, in, host, nx, nz, gcx, gcz); break;
		case nse_const3d::axisYZ:
			mpi_com.scatter_yz<memOUT, memIN>(out, in, host, ny, nz, gcy, gcz); break;
		case nse_const3d::axisXYZ:
			mpi_com.scatter<memOUT, memIN>(out, in, host, nx, ny, nz, gcx, gcy, gcz); break;
		default: return;
		}
	}

	template< typename T, memType mem  >
	template< memType memOUT >
	void Grid3d< T, mem >::mpi_gather_center_coord(T* _RESTRICT out,
		const int host, const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX:
			mpi_com.gather_x<memOUT, mem>(out, px, host, nx, gcx); break;
		case nse_const3d::axisY:
			mpi_com.gather_y<memOUT, mem>(out, py, host, ny, gcy); break;
		case nse_const3d::axisZ:
			mpi_com.gather_z<memOUT, mem>(out, pz, host, nz, gcz); break;
		default: return;
		}
	}

	template< typename T, memType mem >
	template< memType memOUT >
	void Grid3d< T, mem >::mpi_gather_edge_coord(T* _RESTRICT out,
		const int host, const nse_const3d::axisType axis) const
	{
		switch (axis)
		{
		case nse_const3d::axisX:
			mpi_com.gather_x<memOUT, mem>(out, ex, host, nx, gcx); break;
		case nse_const3d::axisY:
			mpi_com.gather_y<memOUT, mem>(out, ey, host, ny, gcy); break;
		case nse_const3d::axisZ:
			mpi_com.gather_z<memOUT, mem>(out, ez, host, nz, gcz); break;
		default: return;
		}
	}

	template< typename T, memType mem >
	template< memType memOUT >
	void Grid3d< T, mem >::mpi_gather_center_coord(T* _RESTRICT xout, T* _RESTRICT yout, T* _RESTRICT zout,
		const int host) const
	{
		mpi_gather_center_coord<memOUT>(xout, host, nse_const3d::axisX);
		mpi_gather_center_coord<memOUT>(yout, host, nse_const3d::axisY);
		mpi_gather_center_coord<memOUT>(zout, host, nse_const3d::axisZ);
	}

	template< typename T, memType mem >
	template< memType memOUT >
	void Grid3d< T, mem >::mpi_gather_edge_coord(T* _RESTRICT xout, T* _RESTRICT yout, T* _RESTRICT zout,
		const int host) const
	{
		mpi_gather_edge_coord<memOUT>(xout, host, nse_const3d::axisX);
		mpi_gather_edge_coord<memOUT>(yout, host, nse_const3d::axisY);
		mpi_gather_edge_coord<memOUT>(zout, host, nse_const3d::axisZ);
	}

	template< typename T, memType mem >
	void Grid3d< T, mem >::set_id(GridId< T >& id) const
	{
		id.init();
		id.set_dim_num(3);	// 3D //

							// domain definition //
		id.set_domain_dim(1, mpi_x, mpi_length);
		id.set_domain_dim(2, mpi_y, mpi_width);
		id.set_domain_dim(3, mpi_z, mpi_height);

		// grid definition //
		id.set_grid_dim(1, mpi_nx, gcx);
		id.set_grid_dim(2, mpi_ny, gcy);
		id.set_grid_dim(3, mpi_nz, gcz);
	}

	template< typename T, memType mem >
	bool Grid3d< T, mem >::check_id(const GridId< T >& id) const
	{
		return id.check(3);	// check - 3D //
	}

	template< typename T, memType mem >
	bool Grid3d< T, mem >::check_id_dims(const GridId< T >& id) const
	{
		int nxid, nyid, nzid, gcxid, gcyid, gczid;
		id.grid_dim(1, &nxid, &gcxid);
		id.grid_dim(2, &nyid, &gcyid);
		id.grid_dim(3, &nzid, &gczid);

		return (
			(mpi_nx == nxid) && (mpi_ny == nyid) && (mpi_nz == nzid) &&
			(gcx == gcxid) && (gcy == gcyid) && (gcz == gczid));
	}


	template< typename T, memType mem >
	void Grid3d< T, mem >::set_id(GridId< T >& id, const nse_const3d::axisType axis) const
	{
		id.init();

		if (axis == nse_const3d::axisXY) set_id(id);
		if (axis == nse_const3d::axisXY) {
			
			id.set_dim_num(2);	// 2D //

			// domain definition //
			id.set_domain_dim(1, mpi_x, mpi_length);
			id.set_domain_dim(2, mpi_y, mpi_width);

			// grid definition //
			id.set_grid_dim(1, mpi_nx, gcx);
			id.set_grid_dim(2, mpi_ny, gcy);
		}
		if (axis == nse_const3d::axisXZ) {

			id.set_dim_num(2);	// 2D //

			// domain definition //
			id.set_domain_dim(1, mpi_x, mpi_length);
			id.set_domain_dim(2, mpi_z, mpi_height);

			// grid definition //
			id.set_grid_dim(1, mpi_nx, gcx);
			id.set_grid_dim(2, mpi_nz, gcz);
		}
		if (axis == nse_const3d::axisYZ) {

			id.set_dim_num(2);	// 2D //

			// domain definition //
			id.set_domain_dim(1, mpi_y, mpi_width);
			id.set_domain_dim(2, mpi_z, mpi_height);

			// grid definition //
			id.set_grid_dim(1, mpi_ny, gcy);
			id.set_grid_dim(2, mpi_nz, gcz);
		}
		if (axis == nse_const3d::axisX) {

			id.set_dim_num(1);	// 1D //

			// domain definition //
			id.set_domain_dim(1, mpi_x, mpi_length);

			// grid definition //
			id.set_grid_dim(1, mpi_nx, gcx);
		}
		if (axis == nse_const3d::axisY) {

			id.set_dim_num(1);	// 1D //

			// domain definition //
			id.set_domain_dim(1, mpi_y, mpi_width);

			// grid definition //
			id.set_grid_dim(1, mpi_ny, gcy);
		}
		if (axis == nse_const3d::axisZ) {

			id.set_dim_num(1);	// 1D //

			// domain definition //
			id.set_domain_dim(1, mpi_z, mpi_height);

			// grid definition //
			id.set_grid_dim(1, mpi_nz, gcz);
		}
	}

	template< typename T, memType mem >
	bool Grid3d< T, mem >::check_id(const GridId< T >& id, const nse_const3d::axisType axis) const
	{
		if ((axis == nse_const3d::axisX) || 
			(axis == nse_const3d::axisY) || 
			(axis == nse_const3d::axisZ)) return id.check(1);		// check - 1D //
		if ((axis == nse_const3d::axisXY) || 
			(axis == nse_const3d::axisXZ) || 
			(axis == nse_const3d::axisYZ)) return id.check(2);	// check - 2D //

		return id.check(3);	// check - 3D //
	}

	template< typename T, memType mem >
	bool Grid3d< T, mem >::check_id_dims(const GridId< T >& id, const nse_const3d::axisType axis) const
	{
		if (axis == nse_const3d::axisX) {
			int nxid, gcxid;
			id.grid_dim(1, &nxid, &gcxid);

			return ((mpi_nx == nxid) && (gcx == gcxid));
		}
		if (axis == nse_const3d::axisY) {
			int nyid, gcyid;
			id.grid_dim(1, &nyid, &gcyid);

			return ((mpi_ny == nyid) && (gcy == gcyid));
		}
		if (axis == nse_const3d::axisZ) {
			int nzid, gczid;
			id.grid_dim(1, &nzid, &gczid);

			return ((mpi_nz == nzid) && (gcz == gczid));
		}
		if (axis == nse_const3d::axisXY) {
			int nxid, nyid, gcxid, gcyid;
			id.grid_dim(1, &nxid, &gcxid);
			id.grid_dim(2, &nyid, &gcyid);

			return (
				(mpi_nx == nxid) && (mpi_ny == nyid) &&
				(gcx == gcxid) && (gcy == gcyid));
		}
		if (axis == nse_const3d::axisXZ) {
			int nxid, nzid, gcxid, gczid;
			id.grid_dim(1, &nxid, &gcxid);
			id.grid_dim(2, &nzid, &gczid);

			return (
				(mpi_nx == nxid) && (mpi_nz == nzid) &&
				(gcx == gcxid) && (gcz == gczid));
		}
		if (axis == nse_const3d::axisYZ) {
			int nyid, nzid, gcyid, gczid;
			id.grid_dim(1, &nyid, &gcyid);
			id.grid_dim(2, &nzid, &gczid);

			return (
				(mpi_ny == nyid) && (mpi_nz == nzid) &&
				(gcy == gcyid) && (gcz == gczid));
		}

		int nxid, nyid, nzid, gcxid, gcyid, gczid;
		id.grid_dim(1, &nxid, &gcxid);
		id.grid_dim(2, &nyid, &gcyid);
		id.grid_dim(3, &nzid, &gczid);

		return (
			(mpi_nx == nxid) && (mpi_ny == nyid) && (mpi_nz == nzid) &&
			(gcx == gcxid) && (gcy == gcyid) && (gcz == gczid));
	}
}
// -------------------------------------------------------------------------------------------- //