
/*******************************************************************************
*
* File flags.c
*
* Copyright (C) 2005, 2008 Martin Luescher
*
* This software is distributed under the terms of the GNU General Public
* License (GPL)
*
* Flags data base input and query programs
*
* The externally accessible functions are
*
*   void set_flags(event_t event)
*     Reports a global event (such as copying the full-lattice gauge fields)
*     to the data base
*
*   void set_blk_flags(blk_grid_t grid,event_t event,int nb)
*     Reports an event on the n'th block of the specified block grid to
*     the data base. If nb coincides with the number of blocks in the local
*     lattice, the event is assumed to have occurred on all the blocks
*
*   int check_flags(task_t task)
*     Checks whether all preparatory steps have been taken for the specified
*     task on the full lattice and returns 0 if this is so. The non-zero return
*     values are
*     1: Some preparatory steps are missing
*     2: The task is unknown to the data base
*
*   int check_blk_flags(blk_grid_t grid,task_t task,int nb)
*     Checks whether all preparatory steps have been taken for the specified
*     task on the block number nb in the given grid and returns 0 if this is
*     so. If nb is equal to the number of blocks in the local lattice, the
*     flags on all blocks are checked. The non-zero return values are
*     1: Some preparatory steps are missing
*     2: The task is unknown to the data base
*     3: The block grid is unkown or not allocated, or the block number nb
*        is out of range
*
*   int query_flags(query_t query)
*     Submits the specified query to the full-lattice data base and returns 1
*     or 0 depending on whether the answer is "yes" or "no". If the query
*     is unknown to the data base, the program returns 2
*
*   int query_blk_flags(blk_grid_t grid,query_t query,int nb)
*     Submits the specified query related to the block number nb on the
*     given grid to the data base and returns 1 or 0 depending on whether
*     the answer is "yes" or "no". If nb is equal to the number of blocks
*     in the local lattice, the answer is "yes" if and only if it is "yes"
*     on all blocks. The other return values are
*     2: The query is unknown to the data base
*     3: The block grid is unkown or not allocated, or the block number nb
*        is out of range
*
*   void print_flags(void)
*     This program prints the current values of all full-lattice flags to
*     stdout from process 0
*
*   void print_blk_flags(blk_grid_t grid,int nb)
*     This program prints the current values of all flags on the block
*     number nb in the specified grid to stdout from process 0. If nb is
*     equal to the number of blocks in the local lattice, the flags on all
*     blocks are printed
*
* Notes:
*
* To ensure the consistency of the data base, the program set_flags() should
* be called on all processes simultaneously. The same applies to the program
* set_blk_flags() if a global event (i.e. one that concerns all the blocks
* in the specified grid) is reported. In all other cases the programs in this
* module do not require any communications and can safely be called locally
*
* The possible block grids, events, tasks and queries are defined in the
* header file flags.h. The actions assocated to the various events, tasks
* queries are defined in the *.h files in the include/flags directory
* (application programs do not need to include these)
*
* For further explanations see the README and README.flags files in this
* directory
*
*******************************************************************************/

#define FLAGS_C

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <limits.h>
#include "mpi.h"
#include "start.h"
#include "flags.h"

#define NFLGS (7+ZOPS+2*BLK_GRIDS)

static struct
{
   int u,ud,ubuf,udbuf;
   int bc,bcd,bcbuf,bcdbuf;
   int sw[3],swd[3];
   int dfl[2],zop[ZOPS][2];
} lat={0,0,0,0,0,0,0,0,{0,0,0},{0,0,0},{0,0},{{0,0}}};

typedef struct
{
   int u,ud;
   int ub,ubd,bc,bcd;
   int sw[3],swd[3],swb;
} blk_flags_t;

typedef struct
{
   int nb;
   int shu,shud,shs,shsd,shsb,shsbd;
   int ub,ubd;
   int ns,nsd,nsb,nsbd;
   blk_flags_t *bf;
} grid_flags_t;

static int init=0,tag=0;
static int nflgs=NFLGS,flgs0[NFLGS],*flgs=NULL;
static const blk_flags_t bf0={0,0,0,0,0,0,{0,0,0},{0,0,0},0};
static const grid_flags_t gf0={0,0,0,0,0,0,0,0,0,0,0,0,0,NULL};
static grid_flags_t gfs[BLK_GRIDS]={{0,0,0,0,0,0,0,0,0,0,0,0,0,NULL}},*gf;


static void set_flgs(void)
{
   int n,k,l;
   blk_flags_t *bf;
   
   if (flgs==NULL)
      flgs=flgs0;

   n=0;
   flgs[n++]=lat.u;
   flgs[n++]=lat.ud;
   flgs[n++]=lat.ubuf;
   flgs[n++]=lat.udbuf;
   flgs[n++]=lat.sw[0];
   flgs[n++]=lat.swd[0];
   flgs[n++]=lat.dfl[0];

   for (k=0;k<ZOPS;k++)
      flgs[n++]=lat.zop[k][0];

   for (k=0;k<BLK_GRIDS;k++)
   {
      flgs[n++]=gfs[k].ub;
      flgs[n++]=gfs[k].ubd;
      bf=gfs[k].bf;

      for (l=0;l<gfs[k].nb;l++)
      {
         flgs[n++]=bf[l].u;
         flgs[n++]=bf[l].ud;
         flgs[n++]=bf[l].ub;
         flgs[n++]=bf[l].ubd;
         flgs[n++]=bf[l].sw[0];
         flgs[n++]=bf[l].swd[0];
         flgs[n++]=bf[l].swb;         
      }
   }
}


static void find_gap(int *a,int *d)
{
   int k,l;
   int fk,h,hmax;

   (*a)=0;
   (*d)=INT_MAX;

   for (k=0;k<nflgs;k++)
   {
      h=flgs[k];

      if ((h>0)&&(h<(*d)))
         (*d)=h;
   }
   
   for (k=0;k<nflgs;k++)
   {
      fk=flgs[k];
      hmax=INT_MAX-fk;

      for (l=0;l<nflgs;l++)
      {
         h=flgs[l]-fk;

         if ((h>0)&&(h<hmax))
         {
            hmax=h;

            if (h<=(*d))
               break;
         }
      }

      if (hmax>(*d))
      {
         (*a)=fk;
         (*d)=hmax;
      }
   }
}


static void compress_flags(void)
{
   int n,k,l,a,d;
   blk_flags_t *bf;

   set_flgs();
   find_gap(&a,&d);
   d-=1;

   for (k=0;k<nflgs;k++)
   {
      if (flgs[k]>a)
         flgs[k]-=d;
   }

   n=0;
   lat.u=flgs[n++];
   lat.ud=flgs[n++];
   lat.ubuf=flgs[n++];
   lat.udbuf=flgs[n++];
   lat.sw[0]=flgs[n++];
   lat.swd[0]=flgs[n++];
   lat.dfl[0]=flgs[n++];

   for (k=0;k<ZOPS;k++)
      lat.zop[k][0]=flgs[n++];

   for (k=0;k<BLK_GRIDS;k++)
   {
      gfs[k].ub=flgs[n++];
      gfs[k].ubd=flgs[n++];
      bf=gfs[k].bf;

      for (l=0;l<gfs[k].nb;l++)
      {
         bf[l].u=flgs[n++];
         bf[l].ud=flgs[n++];
         bf[l].ub=flgs[n++];
         bf[l].ubd=flgs[n++];
         bf[l].sw[0]=flgs[n++];
         bf[l].swd[0]=flgs[n++];
         bf[l].swb=flgs[n++];         
      }
   }

   tag-=d;
}


static int next_tag(void)
{
   if (tag==INT_MAX)
      compress_flags();
   tag+=1;

   return tag;
}


static void alloc_flgs(void)
{
   int n,k;
   
   n=0;

   for (k=0;k<BLK_GRIDS;k++)
      n+=gfs[k].nb;

   n=NFLGS+7*n;

   if (n==nflgs)
      return;

   if (nflgs>NFLGS)
      afree(flgs);

   if (n>NFLGS)
   {
      flgs=amalloc(n*sizeof(*flgs),3);
      error(flgs==NULL,1,"alloc_flgs [flags.c]",
            "Unable to allocate auxiliary array"); 
   }
   else
      flgs=NULL;
   
   nflgs=n;
}


static void free_blk_flags(void)
{
   int l;
   
   if ((*gf).bf==NULL)
      return;

   afree((*gf).bf);
   (*gf)=gf0;

   if (gf==&gfs[DFL_BLOCKS])
   {
      lat.dfl[0]=0;
      lat.dfl[1]=0;

      for (l=0;l<ZOPS;l++)
      {
         lat.zop[l][0]=0;
         lat.zop[l][1]=0;
      }
   }

   alloc_flgs();
}


static void alloc_blk_flags(int nb)
{
   int n,nt;
   blk_flags_t *bf,bf1;
   //hmc_parms_t hmc;
   //dfl_parms_t dfl;
   
   if ((*gf).bf!=NULL)
      free_blk_flags();

   nt=next_tag();
   
   bf=amalloc(nb*sizeof(blk_flags_t),3);
   error(bf==NULL,1,"alloc_blk_flags [flags.c]",
         "Unable to allocate flags data base");

   (*gf).nb=nb;
   bf1=bf0;

   if (gf==&gfs[GCR_BLOCKS])
   {
      (*gf).shs=1;
      (*gf).ns=3;
      bf1.u=nt;
      bf1.ub=bf1.u;
   }
   /*
   else if (gf==&gfs[HMC_BLOCKS])
   {
      (*gf).shu=1;
      (*gf).shs=1;
      (*gf).shsd=1;
      (*gf).shsbd=1;
      (*gf).ns=5;

      hmc=hmc_parms();
      n=hmc.cno_blk;
      
      if (n<6)
         (*gf).nsd=7;
      else
         (*gf).nsd=n+2;

      (*gf).nsbd=1;
      bf1.ud=nt;
      bf1.ubd=bf1.ud;
   }
   else if (gf==&gfs[DFL_BLOCKS])
   {
      (*gf).shud=1;

      dfl=dfl_parms();
      n=dfl.Ns+1;

      (*gf).ns=n;
      (*gf).nsd=n;
   }
   */
   for (n=0;n<nb;n++)
      bf[n]=bf1;

   /*
   if (gf==&gfs[HMC_BLOCKS])
   {
      bf[0].u=bf[0].ud;
      bf[0].ub=bf[0].ubd;
   }

   if (gf==&gfs[DFL_BLOCKS])
   {
      bf[0].ud=nt;
      bf[0].ubd=bf[0].ud;
   }   
   */
   (*gf).bf=bf;
   alloc_flgs();
}


#include "flags/events.h"
#include "flags/tasks.h"
#include "flags/queries.h"
#include "flags/blk_events.h"
#include "flags/blk_tasks.h"
#include "flags/blk_queries.h"


static void set_arrays(void)
{
   set_events();
   set_tasks();
   set_queries();

   set_blk_events();
   set_blk_tasks();
   set_blk_queries();

   init=1;
}


static void set_gf(blk_grid_t grid)
{
   if ((grid>=0)&&(grid<BLK_GRIDS))
      gf=&gfs[grid];
   else
      gf=NULL;
}


void set_flags(event_t event)
{
   int iprms[1];

   iprms[0]=event;
   MPI_Bcast(iprms,1,MPI_INT,0,MPI_COMM_WORLD);
   error(iprms[0]!=event,1,
         "set_flags [flags.c]","Parameter is not global");

   if (init==0)
      set_arrays();

   error_root((event<0)||(event>=EVENTS),1,
              "set_flags [flags.c]","Unknown event");
   error_root(event_fcts[event]==NULL,1,
              "set_flags [flags.c]","No action associated to event");
   event_fcts[event]();
}


void set_blk_flags(blk_grid_t grid,event_t event,int nb)
{
   if (init==0)
      set_arrays();
   
   set_gf(grid);
   
   if (error_loc(gf==NULL,1,"set_blk_flags [flags.c]","Unknown block grid"))
      return;

   if (event==ALLOCATED_BLOCK_GRID)
      alloc_blk_flags(nb);
   else if (event==FREED_BLOCK_GRID)
      free_blk_flags();
   else 
   {
      if ((error_loc((*gf).bf==NULL,1,"set_blk_flags [flags.c]",
                     "Block grid is not allocated"))||
          (error_loc((nb<0)||(nb>(*gf).nb),1,"set_blk_flags [flags.c]",
                     "Block number out of range"))||
          (error_loc((event<0)||(event>=EVENTS)||
                     (blk_event_fcts[event]==NULL),1,"set_blk_flags [flags.c]",
                     "Unknown event")))
         return;
      
      blk_event_fcts[event](nb);
   }
}


int check_flags(task_t task)
{
   if (init==0)
      set_arrays();

   if ((task<0)||(task>=TASKS)||(task_fcts[task]==NULL))
      return 2;
   else
      return (task_fcts[task]())^0x1;
}


int check_blk_flags(blk_grid_t grid,task_t task,int nb)
{
   if (init==0)
      set_arrays();

   set_gf(grid);

   if ((gf==NULL)||((*gf).bf==NULL)||(nb<0)||(nb>(*gf).nb))
      return 3;
   else if ((task<0)||(task>=TASKS)||(blk_task_fcts[task]==NULL))
      return 2;
   else
      return (blk_task_fcts[task](nb))^0x1;
}


int query_flags(query_t query)
{
   if (init==0)
      set_arrays();

   if ((query<0)||(query>=QUERIES)||(query_fcts[query]==NULL))
      return 2;
   else
      return query_fcts[query]();
}


int query_blk_flags(blk_grid_t grid,query_t query,int nb)
{
   if (init==0)
      set_arrays();

   set_gf(grid);

   if ((gf==NULL)||((*gf).bf==NULL)||(nb<0)||(nb>(*gf).nb))
      return 3;
   else if ((query<0)||(query>=QUERIES)||(blk_query_fcts[query]==NULL))
      return 2;
   else
      return blk_query_fcts[query](nb);
}


void print_flags(void)
{
   int my_rank,l;

   MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);

   if (my_rank==0)
   {
      printf("Full lattice flags:\n");
      printf("u,ud         = %d,%d\n",lat.u,lat.ud);
      printf("ubuf,udbuf   = %d,%d\n",lat.ubuf,lat.udbuf);
      printf("bc,bcd       = %d,%d\n",lat.bc,lat.bcd);
      printf("bcbuf,bcdbuf = %d,%d\n",lat.bcbuf,lat.bcdbuf);
      printf("sw           = %d,%d,%d\n",lat.sw[0],lat.sw[1],lat.sw[2]);
      printf("swd          = %d,%d,%d\n",lat.swd[0],lat.swd[1],lat.swd[2]);
      printf("dfl          = %d,%d\n",lat.dfl[0],lat.dfl[1]);
      
      for (l=0;l<ZOPS;l++)
         printf("zop[%d]       = %d,%d\n",
                l,lat.zop[l][0],lat.zop[l][1]);

      printf("\n");
   }
}


void print_blk_flags(blk_grid_t grid,int nb)
{
   int my_rank,n;
   blk_flags_t *bf;

   MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);

   if (my_rank==0)
   {
      set_gf(grid);
      error_root((gf==NULL)||((*gf).bf==NULL),1,"print_blk_flags [flags.c]",
                 "Unknown or unallocated block grid");
      error_root((nb<0)||(nb>(*gf).nb),1,"print_blk_flags [flags.c]",
                 "Block number out of range");

      if ((nb==0)||(nb==(*gf).nb))
      {
         if (grid==GCR_BLOCKS)
            printf("Flags on the GCR block grid:\n");
	 /*
         else if (grid==HMC_BLOCKS)
            printf("Flags on the HMC block grid:\n");
         else if (grid==DFL_BLOCKS)
            printf("Flags on the DFL block grid:\n");
	 */
         printf("shu,shud     = %d,%d\n",(*gf).shu,(*gf).shud);
         printf("shs,shsd     = %d,%d\n",(*gf).shs,(*gf).shsd);
         printf("shsb,shsbd   = %d,%d\n",(*gf).shsb,(*gf).shsbd);
         printf("ub,ubd       = %d,%d\n",(*gf).ub,(*gf).ubd);
         printf("ns,nsd       = %d,%d\n",(*gf).ns,(*gf).nsd);
         printf("nsb,nsbd     = %d,%d\n\n",(*gf).nsb,(*gf).nsbd);
      }

      bf=(*gf).bf;

      for (n=0;n<(*gf).nb;n++)
      {
         if ((nb==n)||(nb==(*gf).nb))
         {
            printf("Flags on block number %d:\n",n);
            printf("u,ud         = %d,%d\n",(*bf).u,(*bf).ud);
            printf("ub,ubd       = %d,%d\n",(*bf).ub,(*bf).ubd);
            printf("bc,bcd       = %d,%d\n",(*bf).bc,(*bf).bcd);
            printf("sw           = %d,%d,%d\n",(*bf).sw[0],(*bf).sw[1],
                   (*bf).sw[2]);
            printf("swd          = %d,%d,%d\n",(*bf).swd[0],(*bf).swd[1],
                   (*bf).swd[2]);
            printf("swb          = %d\n\n",(*bf).swb);
         }

         bf+=1;
      }
   }
}
