
/*******************************************************************************
*
* File block.c
*
* Copyright (C) 2005 Martin Luescher
*
* This software is distributed under the terms of the GNU General Public
* License (GPL)
*
* Basic allocation programs for blocks of lattice points
*
* The externally accessible functions are
*
*   void alloc_blk(block_t *b,int *bo,int *bs,int iu,int iud,int ns,int nsd)
*     Sets the offset and side-lengths of the block b to bo[4] and bs[4],
*     respectively, and allocates the block fields depending on the values
*     of the other parameters. The single-precision gauge and pauli fields
*     are allocated if iu=1, the double-precision gauge and pauli fields if
*     iud=1, while ns and nsd are the numbers of single- and double-precision
*     Dirac fields that are allocated
*
*   void alloc_bnd(block_t *b,int ns,int nsd)
*     Extends the gauge fields to the boundary (*b).bb of the block b and
*     allocates ns (nsd) single-precision (double-precision) Weyl fields
*     at the boundary
*
*   void free_blk(block_t *b)
*     Frees the arrays in the block b that were previously allocated by
*     alloc_blk(). The boundary fields are also freed if they were allocated
*     and all entries in the block structure are then set to zero
*
*   int ipt_blk(block_t *b,int *x)
*     Returns the index of the point in the block b with local cartesian
*     coordinates x[4]
*
*   int dist_bndry(block_t *b,int ix)
*     Returns the distance of the point in the block b with index ix from
*     the exterior boundary of the block
*
* Notes:
*
* It is currently not possible to allocate blocks that are not fully
* contained in the local lattice. Their exterior boundary may, however,
* overlap with the lattices on the neighbouring processes. In all cases
* the geometry arrays are properly initialized and the allocated fields
* are set to 1 (gauge and pauli fields) or 0 (spinor and weyl fields)
*
* Block allocation is a global operation, i.e. alloc_blk(), alloc_bnd() and
* free_blk() must be called on all processes simultaneously. The other
* programs can be called locally and any errors found there are registered
* by the error_loc [utils.c] function
*
* alloc_blk() registers the blocks internally as being allocated. In this
* way it is possible to exclude any misuses of the programs such as freeing
* an unallocated block (which could have unpredictable side-effects). An
* already allocated block is first freed and then reallocated by alloc_blk().
*
* Evidently alloc_bnd() can only be applied to an allocated block (the program
* terminates with an error message if it is not). An already allocated block
* boundary is first freed and then reallocated by alloc_bnd()
*
* Protected blocks and block boundaries cannot be freed or reallocated
*
*******************************************************************************/

#define BLOCK_C

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "mpi.h"
#include "su3.h"
#include "start.h"
#include "block.h"
#include "global.h"

static const su3 u0={{{{0.0f}}}};
static const su3_dble ud0={{{{0.0}}}};
static const pauli p0={{0.0f}};
static const pauli_dble pd0={{0.0}};

struct ablk_t
{
   block_t *b;
   struct ablk_t *next;
};

static struct ablk_t *first=NULL;


static void ins_blk(block_t *b)
{
   struct ablk_t *p;

   p=malloc(sizeof(*p));
   (*p).b=b;
   (*p).next=first;
   first=p;
}


static void rmv_blk(block_t *b)
{
   struct ablk_t *p,*q;

   q=NULL;

   for (p=first;p!=NULL;p=(*p).next)
   {
      if ((*p).b==b)
      {
         if (q==NULL)
            first=(*p).next;
         else
            (*q).next=(*p).next;

         free(p);
         return;
      }

      q=p;
   }
}


static int fnd_blk(block_t *b)
{
   struct ablk_t *p;

   for (p=first;p!=NULL;p=(*p).next)
   {
      if ((*p).b==b)
         return 1;
   }

   return 0;
}


static void free_bnd(block_t *b)
{
   int n,shu,shud,shs,shsd;
   bndry_t *bb;

   bb=b->bb;
   n=b->shf>>5;

   error((bb!=NULL)&&((n&0x1)!=0),1,"free_bnd [block.c]",
            "Block boundary is protected");

   if (bb==NULL)
      return;

   shu =n&0x2;
   shud=n&0x4;
   shs =n&0x8;
   shsd=n&0x10;

   afree(bb->ipp);

   if ((bb->u!=NULL)&&(shu==0))
      afree(bb->u);

   if ((bb->ud!=NULL)&&(shud==0))
      afree(bb->ud);

   if ((bb->ns>0)&&(shs==0))
   {
      afree(bb->s[0]);
      afree(bb->s);
   }

   if ((bb->nsd>0)&&(shsd==0))
   {
      afree(bb->sd[0]);
      afree(bb->sd);
   }

   afree(b->bb);
   b->bb=NULL;
   b->shf&=(~0x3e0);
}


void free_blk(block_t *b)
{
   int n,shu,shud,shs,shsd;

   if (fnd_blk(b)==0)
      return;

   free_bnd(b);
   n=b->shf;

   error(((n&0x1)!=0),1,"free_blk [block.c]","Block is protected");

   shu =n&0x2;
   shud=n&0x4;
   shs =n&0x8;
   shsd=n&0x10;

   afree(b->bo);
   b->bo=NULL;
   b->bs=NULL;

   b->vol=0;
   b->volh=0;
   b->vbb=0;

   afree(b->iup);
   afree(b->ipt);
   b->iup=NULL;
   b->idn=NULL;
   b->ipt=NULL;
   b->imb=NULL;

   if ((b->u!=NULL)&&(shu==0))
   {
      afree(b->u);
      afree(b->sw);
   }

   if ((b->ud!=NULL)&&(shud==0))
   {
      afree(b->ud);
      afree(b->swd);
   }

   if ((b->ns>0)&&(shs==0))
   {
      afree(b->s[0]);
      afree(b->s);
   }

   if ((b->nsd>0)&&(shsd==0))
   {
      afree(b->sd[0]);
      afree(b->sd);
   }

   b->ns=0;
   b->nsd=0;
   b->shf=0;
   b->u=NULL;
   b->ud=NULL;
   b->sw=NULL;
   b->swd=NULL;
   b->swb=NULL;
   b->s=NULL;
   b->sd=NULL;

   rmv_blk(b);
}


static void set_u2unity(int vol,su3 *u)
{
   su3 unity,*um;

   unity=u0;
   unity.c11.re=1.0f;
   unity.c22.re=1.0f;
   unity.c33.re=1.0f;

   um=u+vol;

   for (;u<um;u++)
      *u=unity;
}


static void set_ud2unity(int vol,su3_dble *ud)
{
   su3_dble unity,*um;

   unity=ud0;
   unity.c11.re=1.0;
   unity.c22.re=1.0;
   unity.c33.re=1.0;

   um=ud+vol;

   for (;ud<um;ud++)
      *ud=unity;
}


static void set_sw2unity(int vol,pauli *p)
{
   pauli unity,*pm;

   unity=p0;
   unity.u[0]=1.0f;
   unity.u[1]=1.0f;
   unity.u[2]=1.0f;
   unity.u[3]=1.0f;
   unity.u[4]=1.0f;
   unity.u[5]=1.0f;

   pm=p+vol;

   for (;p<pm;p++)
      *p=unity;
}


static void set_swd2unity(int vol,pauli_dble *pd)
{
   pauli_dble unity,*pm;

   unity=pd0;
   unity.u[0]=1.0;
   unity.u[1]=1.0;
   unity.u[2]=1.0;
   unity.u[3]=1.0;
   unity.u[4]=1.0;
   unity.u[5]=1.0;

   pm=pd+vol;

   for (;pd<pm;pd++)
      *pd=unity;
}


static int check_parms(int *bo,int *bs,int iu,int iud,int ns,int nsd)
{
   int mu,iprms[12];

   for (mu=0;mu<4;mu++)
   {
      iprms[mu]=bo[mu];
      iprms[4+mu]=bs[mu];
   }

   iprms[8]=iu;
   iprms[9]=iud;
   iprms[10]=ns;
   iprms[11]=nsd;

   MPI_Bcast(iprms,12,MPI_INT,0,MPI_COMM_WORLD);

   for (mu=0;mu<4;mu++)
      if ((iprms[mu]!=bo[mu])||(iprms[4+mu]!=bs[mu]))
         return 1;

   if ((iprms[8]!=iu)||(iprms[9]!=iud)||(iprms[10]!=ns)||(iprms[11]!=nsd))
      return 1;

   return 0;
}


void alloc_blk(block_t *b,int *bo,int *bs,int iu,int iud,int ns,int nsd)
{
   int n,mu;

   free_blk(b);

   error(check_parms(bo,bs,iu,iud,ns,nsd)!=0,1,
         "alloc_blk [block.c]","Parameters are not global");

   error_root(
         (bs[0]<2)||(bo[0]<0)||((bo[0]+bs[0])>L0)||((bs[0]%2)!=0)||
         (bs[1]<2)||(bo[1]<0)||((bo[1]+bs[1])>L1)||((bs[1]%2)!=0)||
         (bs[2]<2)||(bo[2]<0)||((bo[2]+bs[2])>L2)||((bs[2]%2)!=0)||
         (bs[3]<2)||(bo[3]<0)||((bo[3]+bs[3])>L3)||((bs[3]%2)!=0),1,
   "alloc_blk [block.c]","Improper choice of block position or size");

   error_root((ns<0)||(nsd<0),1,"alloc_blk [block.c]",
               "Improper choice of the numbers of spinor fields");

   b->bo=amalloc(8*sizeof(int),3);
   error(b->bo==NULL,1,
           "alloc_blk [block.c]","Unable to allocate size arrays");
   b->bs=b->bo+4;

   for (mu=0;mu<4;mu++)
   {
      b->bo[mu]=bo[mu];
      b->bs[mu]=bs[mu];
   }

   b->vol=bs[0]*bs[1]*bs[2]*bs[3];
   b->volh=b->vol/2;
   b->vbb=2*(bs[0]*bs[1]*bs[2]+bs[1]*bs[2]*bs[3]+
         bs[2]*bs[3]*bs[0]+bs[3]*bs[0]*bs[1]);
   b->ns=ns;
   b->nsd=nsd;
   b->shf=0;

   b->iup=amalloc(2*b->vol*sizeof(*(b->iup)),3);
   b->ipt=amalloc(2*(b->vol+1)*sizeof(int),3);
   error((b->iup==NULL)||(b->ipt==NULL),1,
            "alloc_blk [block.c]","Unable to allocate the geometry arrays");
   b->idn=b->iup+b->vol;
   b->imb=b->ipt+b->vol+1;

   if (iu==1)
   {
      b->u=amalloc(b->volh*sizeof(*b->u),ALIGN);
      error(b->u==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the gauge field array b->u");
      set_u2unity(4*b->vol,b->u[0]);

      b->sw=amalloc(2*b->vol*sizeof(pauli),ALIGN);
      error(b->sw==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the SW-term b->sw");
      set_sw2unity(2*b->vol,b->sw);
   }
   else
   {
      b->u=NULL;
      b->sw=NULL;
   }

   if (iud==1)
   {
      b->ud=amalloc(b->volh*sizeof(*b->ud),ALIGN);
      error(b->ud==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the gauge field array b->ud");
      set_ud2unity(4*b->vol,b->ud[0]);

      b->swd=amalloc(4*b->vol*sizeof(pauli_dble),ALIGN);
      error(b->swd==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the SW-term b->swd");
      b->swb=b->swd+2*b->vol;
      set_swd2unity(4*b->vol,b->swd);
   }
   else
   {
      b->ud=NULL;
      b->swd=NULL;
      b->swb=NULL;
   }

   if (ns>0)
   {
     b->s=amalloc(ns*sizeof(*(b->s)),3);
      error(b->s==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the pointer array b->s");

      b->s[0]=amalloc(ns*(b->vol+1)*sizeof(spinor),ALIGN);
      error(b->s[0]==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the single-precision spinor fields");

      for (n=1;n<ns;n++)
         b->s[n]=b->s[n-1]+b->vol+1;

      set_s2zero(ns*(b->vol+1),b->s[0]);
   }
   else
      b->s=NULL;

   if (nsd>0)
   {
      b->sd=amalloc(nsd*sizeof(*b->sd),3);
      error(b->sd==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the pointer array b->sd");

      b->sd[0]=amalloc(nsd*(b->vol+1)*sizeof(spinor_dble),ALIGN);
      error(b->sd[0]==NULL,1,"alloc_blk [block.c]",
              "Unable to allocate the double-precision spinor fields");

      for (n=1;n<nsd;n++)
         b->sd[n]=b->sd[n-1]+b->vol+1;

      set_sd2zero(nsd*(b->vol+1),b->sd[0]);
   }
   else
      b->sd=NULL;

   b->bb=NULL;
   block_geometry(b);
   ins_blk(b);
}


void alloc_bnd(block_t *b,int ns,int nsd)
{
   int iu,iud;
   int vol,ifc,n,*ipp,*imb;
   su3 *u;
   su3_dble *ud;
   weyl **s,*sb;
   weyl_dble **sd,*sdb;
   bndry_t *bb;

   error(fnd_blk(b)==0,1,
         "alloc_bnd [block.c]","Block is not allocated");

   free_bnd(b);

   iu=(b->u!=NULL);
   iud=(b->ud!=NULL);

   error(check_parms(b->bo,b->bs,iu,iud,ns,nsd)!=0,1,
         "alloc_bnd [block.c]","Parameters are not global");

   error_root((ns<0)||(nsd<0),1,"alloc_bnd [block.c]",
               "Improper choice of the numbers of Weyl fields");

   error((b->bb!=NULL)&&((b->shf&0x2)!=0),1,"alloc_bnd [block.c]",
            "Block boundary is already allocated and protected");

   b->bb=amalloc(8*sizeof(*bb),3);
   error(b->bb==NULL,1,"alloc_bnd [block.c]",
           "Unable to allocate boundary structures");

   vol=b->vbb;
   ipp=amalloc(2*(vol+8)*sizeof(int),3);
   error(ipp==NULL,1,"alloc_bnd [block.c]",
         "Unable to allocate the geometry arrays");
   imb=ipp+vol+8;

   if (iu==1)
   {
      u=amalloc(vol*sizeof(su3),ALIGN);
      error(u==NULL,1,"alloc_bnd [block.c]",
            "Unable to allocate the single-precision gauge field");
      set_u2unity(vol,u);
   }
   else
      u=NULL;

   if (iud==1)
   {
      ud=amalloc(vol*sizeof(su3_dble),ALIGN);
      error(ud==NULL,1,"alloc_bnd [block.c]",
            "Unable to allocate the double-precision gauge field");
      set_ud2unity(vol,ud);
   }
   else
      ud=NULL;

   if (ns>0)
   {
      s=amalloc(8*ns*sizeof(*s),3);
      sb=amalloc(ns*vol*sizeof(weyl),ALIGN);
      error((s==NULL)||(sb==NULL),1,"alloc_bnd [block.c]",
            "Unable to allocate the single-precision Weyl fields");
      set_s2zero(ns*(vol/2),(spinor*)(sb));
   }
   else
   {
      s=NULL;
      sb=NULL;
   }

   if (nsd>0)
   {
      sd=amalloc(8*nsd*sizeof(*sd),3);
      sdb=amalloc(nsd*vol*sizeof(weyl_dble),ALIGN);
      error((sd==NULL)||(sdb==NULL),1,"alloc_bnd [block.c]",
            "Unable to allocate the double-precision Weyl fields");
      set_sd2zero(nsd*(vol/2),(spinor_dble*)(sdb));
   }
   else
   {
      sd=NULL;
      sdb=NULL;
   }

   bb=b->bb;

   for (ifc=0;ifc<8;ifc++)
   {
      bb->ifc=ifc;
      bb->vol=b->vol/b->bs[ifc/2];
      bb->ns=ns;
      bb->nsd=nsd;

      bb->ipp=ipp;
      bb->imb=imb;
      ipp+=(bb->vol+1);
      imb+=(bb->vol+1);

      if (iu==1)
      {
         bb->u=u;
         u+=bb->vol;
      }
      else
         bb->u=NULL;

      if (iud==1)
      {
         bb->ud=ud;
         ud+=bb->vol;
      }
      else
         bb->ud=NULL;

      if (ns>0)
      {
         bb->s=s;

         for (n=0;n<ns;n++)
         {
            *s=sb+n*vol;
            s+=1;
         }

         sb+=bb->vol;
      }
      else
         bb->s=NULL;

      if (nsd>0)
      {
         bb->sd=sd;

         for (n=0;n<nsd;n++)
         {
            *sd=sdb+n*vol;
            sd+=1;
         }

         sdb+=bb->vol;
      }
      else
         bb->sd=NULL;

      bb+=1;
   }

   bndry_geometry(b);
}


int ipt_blk(block_t *b,int *x)
{
   int ix,mu,n;

   n=0;
   ix=0;

   for (mu=0;mu<4;mu++)
   {
      if ((x[mu]<0)||(x[mu]>=b->bs[mu]))
         n=1;

      ix=x[mu]+b->bs[mu]*ix;
   }

   if (error_loc(n,1,"ipt_blk [block.c]",
       "Point coordinates out of range")!=0)
      return 0;
   else
      return b->ipt[ix];
}


int dist_bndry(block_t *b,int ix)
{
   int d,dp,dmin;
   int mu,iy;

   if (error_loc((ix<0)||(ix>b->vol),1,"dist_bndry [block.c]",
       "Point index out of range")!=0)
      return 0;

   dmin=b->bs[0];

   for (mu=0;mu<4;mu++)
   {
      d=0;

      for (iy=ix;iy<b->vol;d++)
         iy=b->iup[iy][mu];

      dp=b->bs[mu]+1-d;

      if (d<dmin)
         dmin=d;
      if (dp<dmin)
         dmin=dp;
   }

   return dmin;
}

#if 0
if(ix < b->volh) { //even point
	 for (j=0;j<4;j++) {
	   if(piup[j]==b->vol) {
	     b->uu[8*ix+2*j]=&u0;
	   }
	   else {
	     //prendi indice spinore e prendo la u che va down
	     b->uu[8*ix+2*j] = b->u[0] + (piup[j] - b->volh)*8 + 2*j + 1;
	   }
	 }
	 
	 for (j=0;j<4;j++) {
	   if(pidn[j]==b->vol) {
	     b->uu[8*ix+2*j+1]=&u0;
	   }
	   else {
	     b->uu[8*ix+2*j+1] = b->u[0] + (pidn[j] - b->volh)*8 + 2*j;
	     
	   }
	 }
       }
       
       else { //odd point
	 u=b->u[0] + 8*(ix- b->volh);
	 for (j=0;j<8;j++) {
	   b->uu[8*ix+j]=u+j;
	 }
	 
       }
       
       

#endif 


void alloc_uidx(block_t *b)
{
  int vol;
  int i, j;
  int *piup,*pidn;

  vol = b->vol;
  
  b->uidx = amalloc((8*vol)*sizeof(int), 6);
  for(i=0;i<vol;i++) {
    piup = b->iup[i];
    pidn = b->idn[i];
    
    if(i < b->volh) { //even point
	 for (j=0;j<4;j++) {
	   if(piup[j]==b->vol) {
	     b->uidx[8*i+2*j]=0;
	   }
	   else {
	     //prendi indice spinore e prendo la u che va down
	     b->uidx[8*i+2*j] = (piup[j] - b->volh)*8 + 2*j + 1;
	   }
	 }
	 
	 for (j=0;j<4;j++) {
	   if(pidn[j]==b->vol) {
	     b->uidx[8*i+2*j+1]=0;
	   }
	   else {
	     b->uidx[8*i+2*j+1] = (pidn[j] - b->volh)*8 + 2*j;
	     
	   }
	 }
       }
       
       else { //odd point

	 for (j=0;j<8;j++) {
	   b->uidx[8*i+j]= 8*(i- b->volh) + j;
	 }
	 
       }
    
  }

}
