/*
 * $Id: Space2PESkewedDistributed.java,v 1.3 1999/10/05 14:25:17 yamauchi Exp $
 *     (M\pi)^2 : Skewed Distributed Allocation Implementation
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source spc2PE.h spc2PE.cc
 *
 *	部分空間と PE のマッピング : Skewed Distributed Allocation
 * 
 * 1999年8月 4日(水)
 */

package jp.ac.tohoku.is.archi.mpi2.common;

import  jp.ac.tohoku.is.archi.mpi2.util.IntVec3;
import  jp.ac.tohoku.is.archi.mpi2.util.Assert;

public class Space2PESkewedDistributed extends Space2PE {
    /** 
     * 平面がどちらを向いているか
     * 	   1次元 ... PE の並ぶ方向
     * 	   2次元 ... PE の並んだ平面の法線方向
     */
    public static final int PE_X_DIR = 0;
    public static final int PE_Y_DIR = 1;
    public static final int PE_Z_DIR = 2;
    public static final int PE_ELSE_DIR = 3;
    
    private int mPlaneDir;	

    public Space2PESkewedDistributed()
    {
	super();
	this.mNumberOfPE 	= new int[3];
	this.mNumberOfSPCof1PE	= new int[3];
	this.mPlaneDir = PE_ELSE_DIR;
    }

    public Space2PESkewedDistributed(Space2PESkewedDistributed rhs)
    {
	this.mSystemDimension = rhs.mSystemDimension;
	this.mNumberOfPE 	= new int[3];
	this.mNumberOfSPCof1PE	= new int[3];
	for(int i = 0; i < 3; i++)
	{
	    this.mNumberOfPE[i]		= rhs.mNumberOfPE[i];
	    this.mNumberOfSPCof1PE[i] 	= rhs.mNumberOfSPCof1PE[i];
	}
	this.mSubDivNum = rhs.mSubDivNum;
	this.mPEID	= new IntVec3(rhs.mPEID);

	// derived
	this.mPlaneDir	= rhs.mPlaneDir;
    }

    /**
     * init
     */
    public void init(IntVec3 numberOfPEs, int subdivnum, IntVec3 PEid)
    {
	this.mSystemDimension = Space2PE.getSystemDimension(numberOfPEs);
	if((this.mSystemDimension < 1) || (2 < this.mSystemDimension))
	{
	    throw new RuntimeException("Error! : illegal system dimension [" +
				       this.mSystemDimension +"] " +
				       "Space2PESkewedDistributed.init fail.");
	    // commMpi2_exit(1);
	}
	this.mSubDivNum	   = subdivnum;    
	if(subdivnum < 1)
	{
	    throw new RuntimeException("Error! : illegal subdivnum " +
				       "Space2PESkewedDistributed.init" +
				       subdivnum);
	    // commMpi2_exit(1);
	}
	for(int i = 0; i < 3; i++)
	{
	    this.mNumberOfPE[i]  = numberOfPEs.get(i);
	    if((this.mNumberOfPE[i]  < 1)  || 
	       ((this.mSubDivNum % this.mNumberOfPE[i]) != 0))
	    {
		//// 空間分割数(ex.32)PE(2,4,4)のようにsubdiv % pe == 0が正しい
		String s = "Error! : illegal PE or space num PE [" + 
		    numberOfPEs + "], spacenum [" + subdivnum + "]" + 
		    "spc2PESkewClass.init";
		throw new RuntimeException(s);
		// commMpi2_exit(1);
	    }
	    this.mNumberOfSPCof1PE[i] = subdivnum / this.mNumberOfPE[i];
	}
	this.mPEID = new IntVec3(PEid);
	
	// set plane dir
	switch(this.mSystemDimension)
	{
	case 1:			// 1 dimension
	    if(this.mNumberOfPE[0] > 1) 
	    {
		this.mPlaneDir = PE_X_DIR;
	    }else if(this.mNumberOfPE[1] > 1)
	    {
		this.mPlaneDir = PE_Y_DIR;	    
	    }else
	    {
		this.mPlaneDir = PE_Z_DIR;	// or there is only 1 PE 
	    }
	    break;
	    
	case 2:			// 2 dimension
	    if((this.mNumberOfPE[0] > 1) && (this.mNumberOfPE[1] > 1))
	    {
		// XY - plane (normal = Z plane)
		this.mPlaneDir = PE_Z_DIR;
	    }else if((this.mNumberOfPE[1] > 1) && (this.mNumberOfPE[2] > 1))
	    {
		// YZ - plane (normal = X plane)
		this.mPlaneDir = PE_X_DIR;
	    }else if((this.mNumberOfPE[2] > 1) && (this.mNumberOfPE[0] > 1))
	    {
		// ZX - plane (normal = Y plane)
		this.mPlaneDir = PE_Y_DIR;
	    }else
	    {
		String s = "Error! : illegal system dimension\n" + 
		    this.baseString() + "\n\tPlaneDir = " + this.mPlaneDir;
		throw new RuntimeException(s);
		// commMpi2_exit(1);
	    }
	    break;
	    
	default:
	    String s = "Error! : PE plane dir is ambiguous.\n" +
		this.baseString() + "\n\tPlaneDir = " + this.mPlaneDir;
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);	
	}
    }

    /**
     *------------------------------------------------------------
     * get PE id (for skewed-distributed allocation 1dim, 2dim, 3dim)
     *------------------------------------------------------------
     * for 1D (x, y, z = spcpos x, y, z)
     * PE_IS_XDIR
     *     int cl_x = (x + y + z) % (SysInfo.getPENum()).get(IntVec3::X);
     *     int cl_y = 0; 
     *     int cl_z = 0; 
     * PE_IS_YDIR
     *     int cl_x = 0; 
     *     int cl_y = (x + y + z) % (SysInfo.getPENum()).get(IntVec3::Y);
     *     int cl_z = 0;
     * PE_IS_ZDIR
     *     int cl_x = 0; 
     *     int cl_y = 0;
     *     int cl_z = (x + y + z) % (SysInfo.getPENum()).get(IntVec3::Z);
     */
    private IntVec3 getNewPE1DID(IntVec3 spcpos) 
    {
	Assert.out(this.mSystemDimension == 1, 
		   "Error!:illegal system dim : getNewPE1DID");
	int pe_x = 0;
	int pe_y = 0;
	int pe_z = 0;
	int sumsp = spcpos.get(IntVec3.X) + spcpos.get(IntVec3.Y) + 
	    spcpos.get(IntVec3.Z);

	switch(this.mPlaneDir) 
	{
	case PE_X_DIR:
	    pe_x = sumsp % this.mNumberOfPE[0];
	    break;
	case PE_Y_DIR:
	    pe_y = sumsp % this.mNumberOfPE[1];
	    break;
	case PE_Z_DIR:
	    pe_z = sumsp % this.mNumberOfPE[2];
	    break;
	default:
	    throw new RuntimeException("Error! : illegal PE plane dir at " + 
				       "Space2PESkewedDistributed.getPE1DID");
	    // commMpi2_exit(1);
	}
	IntVec3 PEID = new IntVec3(pe_x, pe_y, pe_z);
	return(PEID);
    }

    /**
     * for 2dim (sx, sy, sz = spcpos x, y, z)
     * PE_IS_NO_Z_DIR
     *     int cl_x = (sx + sz) % (SysInfo.getPENum()).get(IntVec3.X);
     *     int cl_y =       sy  % (SysInfo.getPENum()).get(IntVec3.Y);
     *     int cl_z = 0;
     *  assert((SysInfo.getPENum()).get(IntVec3.Z) == 1); // Z方向にはない2次元
     * PE_IS_NO_X_DIR
     *     int cl_x = 0;
     *     int cl_y = (sy + sx) % (SysInfo.getPENum()).get(IntVec3.Y);
     *     int cl_z =       sz  % (SysInfo.getPENum()).get(IntVec3.Z);
     *  assert((SysInfo.getPENum()).get(IntVec3.X) == 1); // X方向にはない2次元
     * PE_IS_NO_Y_DIR
     *     int cl_x =       sx  % (SysInfo.getPENum()).get(IntVec3.X);
     *     int cl_y = 0;
     *     int cl_z = (sz + sy) % (SysInfo.getPENum()).get(IntVec3.Z);
     */
    private IntVec3 getNewPE2DID(IntVec3 spcpos) 
    {
	Assert.out(this.mSystemDimension == 2, 
		   "Error! : illegal SystemDImension : getNewPE2DID");
	int sx = spcpos.get(IntVec3.X);
	int sy = spcpos.get(IntVec3.Y);
	int sz = spcpos.get(IntVec3.Z);
	int pe_x, pe_y, pe_z;
	switch(this.mPlaneDir)
	{
	case PE_X_DIR:		// YZ plane
	    pe_x = 0;
	    pe_y = (sy + sx) % this.mNumberOfPE[1];
	    pe_z =       sz  % this.mNumberOfPE[2];
	    Assert.out(this.mNumberOfPE[0] == 1, // X方向にはない2次元
		       "Error! : getNewPE2DID : PE_X_DIR");
	    break;
	    
	case PE_Y_DIR:		// ZX plane
	    pe_x =       sx  % this.mNumberOfPE[0];
	    pe_y = 0;
	    pe_z = (sz + sy) % this.mNumberOfPE[2];
	    Assert.out(this.mNumberOfPE[1] == 1, // Y方向にはない2次元	
		       "Error! : getNewPE2DID : PE_Y_DIR");
	    break;
	    
	case PE_Z_DIR:		// XY plane
	    pe_x = (sx + sz) % this.mNumberOfPE[0];
	    pe_y =       sy  % this.mNumberOfPE[1];
	    pe_z = 0;
	    Assert.out(this.mNumberOfPE[2] == 1, // Z方向にはない2次元
		       "Error! : getNewPE2DID : PE_Z_DIR");
	    break;
	    
	default:
	    throw new RuntimeException("Error! : illegal planedir at " +
				       "Space2PESkewedDistributed.getPE2DID");
	    // commMpi2_exit(1);
	}
	return(new IntVec3(pe_x, pe_y, pe_z));
    }
    
    /**
     * getPE ID for skewed-distributed allocation
     */
    public IntVec3 getNewPEIDfromSpcPos(IntVec3 spcPos)
    {
	Assert.out(spcPos.get(IntVec3.X) >= 0, "Error!: illegal spcpos X");
	Assert.out(spcPos.get(IntVec3.Y) >= 0, "Error!: illegal spcpos Y");
	Assert.out(spcPos.get(IntVec3.Z) >= 0, "Error!: illegal spcpos Z");
	switch(this.mSystemDimension)
	{
	case 1:
	    return(this.getNewPE1DID(spcPos));
	case 2:	
	    return(this.getNewPE2DID(spcPos));
	case 3:
	default:
	    String s = "Internal Error! : no such system dimension for " +
		"skewed-distributed allocation " + this.mSystemDimension;
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);
	}
	// return(new IntVec3(-1, -1, -1));
    }
    
    /**----------------------------------------------------------
     * getPE local index of space array 
     *  	skewed-distributed allocation
     *-----------------------------------------------------------
     * for 1dim
     */
    private IntVec3 getNewPE1DLocalSpaceIndex(IntVec3 spcpos) 
    {
	Assert.out(this.mSystemDimension == 1, "getPE1DLocalSpaceIndex");
	int PEdir, other1, other2;
	switch(this.mPlaneDir) 
	{
	case PE_X_DIR:
	    PEdir 	= 0;	other1	= 1;	other2	= 2;
	    break;
	case PE_Y_DIR:
	    PEdir 	= 1;	other1	= 2;	other2	= 0;
	    break;
	case PE_Z_DIR:
	    PEdir 	= 2;	other1	= 0;	other2	= 1;
	    break;
	default:
	    String s = "Error! : illegal PE plane dir at spc2PESkewClass." +
		"getNewPE1DLocalSpaceIndex";
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);
	}
	int [] sidx = new int[3];
	sidx[0] = spcpos.get(IntVec3.X);
	sidx[1] = spcpos.get(IntVec3.Y);
	sidx[2] = spcpos.get(IntVec3.Z);
	// skewbias : skew によって発生する distributed allocation からのずれ
	int skewbias = (sidx[other1] + sidx[other2]) % this.mNumberOfPE[PEdir];
	sidx[PEdir]  = 
	    ((sidx[PEdir] - skewbias) + this.mSubDivNum) % this.mSubDivNum; 
	
	// ここまでで distributed allocation と同じになった
	sidx[PEdir] /= this.mNumberOfPE[PEdir];
	
	Assert.out((0 <= sidx[0]) && (sidx[0] < this.mNumberOfSPCof1PE[0]),
		   "illegal index X");
	Assert.out((0 <= sidx[1]) && (sidx[1] < this.mNumberOfSPCof1PE[1]),
		   "illegal index Y");
	Assert.out((0 <= sidx[2]) && (sidx[2] < this.mNumberOfSPCof1PE[2]),
		   "illegal index Z");
	
	return(new IntVec3(sidx[0], sidx[1], sidx[2]));
    }

    /**
     * for 2dim
     */
    private IntVec3 getNewPE2DLocalSpaceIndex(IntVec3 spcpos) 
    {
	Assert.out(this.mSystemDimension == 2, 
		   "illegal system dim: getNewPE2DLocalSpaceIndex");
	int PEnormZ, PEdirX, PEdirY;
	switch(this.mPlaneDir)
	{
	case PE_X_DIR:		// YZ plane
	    PEdirX = 1;	PEdirY = 2;	PEnormZ = 0;
	    Assert.out(this.mNumberOfPE[0] == 1, // X方向にはない2次元
		       "getNewPE2DLocalSpaceIndex X");
	    break;
	    
	case PE_Y_DIR:		// ZX plane
	    PEdirX = 2;	PEdirY = 0;	PEnormZ = 1;
	    Assert.out(this.mNumberOfPE[1] == 1, // Y方向にはない2次元	
		       "getNewPE2DLocalSpaceIndex Y");
	    break;
	    
	case PE_Z_DIR:		// XY plane
	    PEdirX = 0;	PEdirY = 1;	PEnormZ = 2;
	    Assert.out(this.mNumberOfPE[2] == 1, // Z方向にはない2次元
		       "getNewPE2DLocalSpaceIndex Z");
	    break;
	    
	default:
	    String s = "Error! : illegal PE plane dir at spc2PESkewClass."
		+ "getPE2DLocalSpaceIndex";
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);
	}
	int [] sidx = new int[3];
	sidx[0] = spcpos.get(IntVec3.X);
	sidx[1] = spcpos.get(IntVec3.Y);
	sidx[2] = spcpos.get(IntVec3.Y);
	int skewbias = sidx[PEnormZ] % this.mNumberOfPE[PEdirX];
	sidx[PEdirX] = 
	    ((sidx[PEdirX] - skewbias) + this.mSubDivNum) % this.mSubDivNum;
	
	// ここまでで distributed allocation と同じになった
	int [] lidx = new int[3];
	lidx[PEdirX]  = sidx[PEdirX]  / this.mNumberOfPE[PEdirX];
	lidx[PEdirY]  = sidx[PEdirY]  / this.mNumberOfPE[PEdirY];
	lidx[PEnormZ] = sidx[PEnormZ];
	
	Assert.out((0 <= lidx[0]) && (lidx[0] < this.mNumberOfSPCof1PE[0]),
		   "illegal lidx 0");
	Assert.out((0 <= lidx[1]) && (lidx[1] < this.mNumberOfSPCof1PE[1]),
		   "illegal lidx 1");		   
	Assert.out((0 <= lidx[2]) && (lidx[2] < this.mNumberOfSPCof1PE[2]),
		   "illegal lidx 2");		   
	
	return(new IntVec3(lidx[0], lidx[1], lidx[2]));
    }
    
    /**
     * get local space array index 
     *   各 PE の持つ local な空間情報配列の index を部分空間位置から返す
     */
    public IntVec3 getNewLocalIdxFromSpcPos(IntVec3 spcPos)
    {
	switch(this.mSystemDimension)
	{
	case 1:
	    return(this.getNewPE1DLocalSpaceIndex(spcPos));
	case 2:
	    return(this.getNewPE2DLocalSpaceIndex(spcPos));
	case 3:
	default:
	    String s = "Internal Error! : no such system dimension for " +
		"skewed-distributed allocation " + this.mSystemDimension;
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);
	}
	// commMpi2_exit(1);
	// return(spcPos);
    }
}
