/******** multican_generic.c *********
 *
 *  This code is meant to be #included from
 *  application-specific multican.c
 *
 *  Multican.c MUST contain:
 *  
 *  #include "lattice.h"
 *
 *  typedef u1h multi_type;                 defines type of MC field
 *  #define multi_field phi                 defines MC field
 *  #define multi_order(i) sqr( phi[i] )    defines MC order param.
 *  
 *  #include "../generic/multican_generic.c"
 *
 *  Exposes:
 *  int is_multicanonical, is_mucacalc,
 *  int setmulti();
 *  double multi_weight();
 *  int mc_acceptance(int parity,double rt);
 *  void set_mc_update(int parity);
 *  void writemuca();
 */

/* MIMD code version 3 */

void calcmulti(), makewarrays();
void multi_calc();
int readmulti();

#define weightwrk "weight_wrk"
#define weightnew "weight.new"

static multi_type *multi_buf;

static int nweight,mc_i;
static double mc_par,weight,w_min,w_max;
static double *mc_lim,*wr1,*wr0,*wrp;
static double *num_sweep;
static double radius[ODD+EVEN+1];
#ifdef is_profile
static double mc_lowerlimit,mc_upperlimit;
#endif

static double mc_delta;
static int tunnel,last_up;

/**************************************************************
 * multi_weight returns just the weight
 */


double multi_weight()
{
#ifndef is_profile
  return(wr1[mc_i]*mc_par + wr0[mc_i]);
#else
  return(0);
#endif
}

/**********************************************************
 * Now global muca routines
 */

void set_mc_update(int parity)
{
  register int i,j;

  /* store the Higgs field */
  j = 0;
  forparity(i,parity) {
    multi_buf[j++] = multi_field[i];
  }
}


/*******************************************************/

int mc_acceptance(int parity,double rad)
{
  register int i,j;
  int ok;

  g_doublesum( &rad, 0 );
    
  if (this_node == 0) {
    double p2,w;
    int ri;

    p2 = radius[opp_parity(parity)] + rad;

    for (ri = mc_i; p2 > mc_lim[ri]; ri++);
    for ( ; p2 < mc_lim[ri]; ri--);

    /** calculate the corrected weight factor **/
    w = wr1[ri]*p2 + wr0[ri] - wr1[mc_i]*mc_par - wr0[mc_i];
	
    nhitmc++;
    if (exp((double)w) >= dran()) {
      /* accept it */
      ahitmc += 1.0;
      mc_par = p2;
      radius[parity] = rad;  /* save radius */
      mc_i = ri;
      ok = 1;
    } else {
      ok = 0;
    }

    if (is_mucacalc) multi_calc();

  } /* this_node */
    
  broadcast_field(&ok,sizeof(int));
    
  if ( !ok ) {
    j = 0;
    forparity(i,parity) {
      multi_field[i] = multi_buf[j++];
    }
  }
  g_sync();
  return ( ok );
}

/***************************************************
 * read in the multicanonical weight function
 */

int readmulti()
{
  FILE *fil;
  int i,j;

  if (this_node == 0) {

    if ((fil = fopen(weightname,"r")) == NULL) {
      printf(" - Non-multicanonical run\n");
      is_mucacalc = is_multicanonical = 0;

    } else {
  
      is_multicanonical = 1;

      printf(" ***** Multicanonical - reading weight file\n");
      i = (fscanf(fil,"%d",&nweight) == 1);
      
      if (i) {
	mc_lim = (double *)calloc(nweight+2,sizeof(double));
	wr0  = (double *)calloc(nweight+2,sizeof(double));
	wr1  = (double *)calloc(nweight+2,sizeof(double));
	wrp = (double *)calloc(nweight+2,sizeof(double));

	for (j=1; j<=nweight && i; j++)
	  i = (fscanf(fil,"%lg %lg",&mc_lim[j],&wrp[j]) == 2);
      }
      if (!i) halt(" ** Read error in weight file");
      fclose(fil);

      mc_lim[0] = -1000; mc_lim[nweight+1] = 1000;
      wrp[0] = wrp[1]; wrp[nweight+1] = wrp[nweight];

      if ((fil = fopen(weightwrk,"r")) != NULL) {
	is_mucacalc = 1;
	
	/* allocate balancing arrays */
	num_sweep = (double *)calloc(nweight+2,sizeof(double));
	
	fscanf(fil,"%lg %lg %lg %d %d",&w_min,&w_max,&mc_delta,&tunnel,&last_up);
	
	printf(" ***** Weight function set up, range %g - %g\n",w_min,w_max);
	printf(" ***** Starting with delta %g, tunnel %d\n",mc_delta,tunnel);

	fclose(fil);
      } else is_mucacalc = 0;

      for (i=0; i<nweight+2; i++) mc_lim[i] *= lattice.volume;

      makewarrays();

      printf(" ***** Weight function read in, %d pivots\n",nweight);

      /* for (i=0; i<nweight+2; i++) printf("%g  %g  %g\n",mc_lim[i],wr0[i],wr1[i]);
       */

    }
  } /* this_node = 0 */

  broadcast_int( &is_multicanonical );

  return( is_multicanonical );
}

/*************************************************************
 *  convert weight to w-arrays
 */

void makewarrays()
{
  int i;

  for (i=0; i<nweight+1; i++) wr1[i] = (wrp[i+1] - wrp[i])/(mc_lim[i+1]-mc_lim[i]);
  wr1[nweight+1] = 0;

  for (i=0; i<nweight+2; i++) wr0[i] = wrp[i] - wr1[i]*mc_lim[i];
}


/*************************************************************
 *  initial set of mc-parameters
 */

int setmulti()
{
  int i,j;

  i = readmulti();

  if (i == 0) return( 0 );   /* No multicanonical run now */

  /* allocate vector buffer */
  i = greater( node.evensites, node.oddsites ) + 1;
  multi_buf = (multi_type *)malloc(i*sizeof(multi_type));
  if (multi_buf == NULL) halt("Multi buffer alloc error");

  radius[EVEN] = radius[ODD] = 0;

  forevensites(i) {
    radius[EVEN] += multi_order(i);
  }
  foroddsites(i) {
    radius[ODD]  += multi_order(i);
  }

  g_doublesum(&radius[EVEN], 0);
  g_doublesum(&radius[ODD], 0);

  mc_par = radius[EVEN] + radius[ODD];

  mc_i = 0;
  if (this_node == 0) {
    while (mc_lim[mc_i] < mc_par) mc_i++;
    mc_i--;

    printf(" **** Initial mc parameter %g, index %d\n",mc_par,mc_i);

    if (is_mucacalc && iteration > 0) {
      /* this is restart status */
      double t;
      FILE *f;

      printf(" **** Reading MUCA-calculation files\n");

      f = fopen(weightnew,"r");

      fscanf(f,"%d\n",&j);
      if (j != nweight) halt("Error in `weight.new'\n");
      for (i=1; i<nweight+1; i++)
	fscanf(f,"%lg %lg\n",&t,&wrp[i]);
      fclose(f);

      makewarrays();
    }
  }
}


/***************************************************
 * accumulate calc-arrays
 */

void multi_calc()
{  
  int id;
  static int nvisit = 0;

  if (mc_par - mc_lim[mc_i] < mc_lim[mc_i+1] - mc_par) id = mc_i;
  else id = mc_i + 1;

  num_sweep[id] += 5;
  if (id > 0) num_sweep[id-1] += 3;
  if (id > 1) num_sweep[id-2] += 1;
  if (id < nweight-1) num_sweep[id+1] += 3;
  if (id < nweight-2) num_sweep[id+2] += 1;
  
  nvisit = (nvisit + 1) % 8;
  if (nvisit == 0) calcmulti();
}

/*************************************************************
 *  calculate muca-function again
 */

void calcmulti()
{
  int i,j,idown,iup;

  idown = -1;
  for (i=0; i<nweight+1; i++) {
    if ( w_min*lattice.volume < mc_lim[i] && w_max*lattice.volume > mc_lim[i] ) {
      if (idown < 0) idown = i; /* first index */
      wrp[i] -= (num_sweep[i] - num_sweep[idown]) * mc_delta/nweight;
      iup = i;
    }      
    else if ( w_max*lattice.volume <= mc_lim[i] ) wrp[i] = wrp[iup];
  }

  if ( last_up && num_sweep[idown] ) {
    tunnel++;
    last_up = 0;
  } else if ( !last_up && num_sweep[iup] ) {
    tunnel++;
    last_up = 1;
  }

  if (tunnel >= 2 ) {
    mc_delta /= 4;
    printf(" ** New multicanonical delta-par: %g\n",mc_delta); fflush(stdout);
    tunnel = 0;
  }

  for (i=0; i<nweight; i++) num_sweep[i] = 0;
  
  for (i=0; i<nweight; i++)
    wr1[i] = (wrp[i+1] - wrp[i])/(mc_lim[i+1]-mc_lim[i]);
  wr1[nweight] = wr1[nweight-1];

  for (i=0; i<=nweight; i++) wr0[i] = wrp[i] - wr1[i]*mc_lim[i];
}

/*************************************************************
 *  Write mc parameters
 */

void writemuca()
{
  int i;
  FILE *f;

  f = fopen(weightnew,"w");

  fprintf(f,"%d\n",nweight);
  for (i=1; i<nweight+1; i++) 
    fprintf(f,"%lg %10lg\n",mc_lim[i]/lattice.volume,wrp[i]);
  fclose(f);

  f = fopen(weightwrk,"w");

  fprintf(f,"%g  %g  %g  %d  %d\n",w_min,w_max,mc_delta,tunnel,last_up);
  fprintf(f,"w_min w_max mc_delta(current) tunnel last_up\n");
  fclose(f);

}
