/*****************************************************
**
**  S-PRES Copyright (c) 2010
**
**  Joshua T Berryman
**
**  This file is part of S-PRES
**
**  S-PRES is Free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  S-PRES is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
**  If you make use of this program for research 
**  resulting in a publication, please cite:
**
**  Joshua T Berryman and Tanja Schilling, 
**  Sampling rare events in nonequilibrium and nonstationary systems. 
**  
**  J Chem Phys, 133(24):244101, Dec 2010.
**
******************************************************/
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <cstring>
#include "shi.h"
#include "../../spres/src/spres.h"


using namespace std;

const double Pi = M_PI;


void shi::run(){

    //run SPRES if required
    evolve( SPRES );

    //insert a new header for the beginning of the MC run
    evolve( MC );

}


void shi::evolve(sim_stage stg){ 


    unsigned int step;
    unsigned int shearCount;

    shearCount = 0;

    if(  stg == SPRES ) {
  
        spres mySpres;

        if( spresSteps > 0 ){

          cerr << "spresSteps: " << spresSteps << endl;

          // run SPRES untill convergence + state->spresNumCompleteMatrices
          mySpres.spresRun( this, int(spresPathsPerBin), long(spresNumBins) );
    
        }
    }
    else if( stg == MC )
    {
        long toSnap = 0;

        //setup stats operations
        if( dynamicsType == SHI_DYNAMICS_NONCONSERVED ){
            stats->requestOperation( "nUp", SHI_STATSOP_NUM_UPSPINS, "" );
            stats->requestOperation( "Energy", SHI_STATSOP_ENERGY, "" );
        }        
        else if( dynamicsType == SHI_DYNAMICS_ASEP ){
            stats->requestOperation( "nUp", SHI_STATSOP_NUM_UPSPINS, "" );
            stats->requestOperation( "nHops", SHI_STATSOP_N_HOPS, "" );
        }        
        
        
        stats->printStatsHeader();
        nHops       = 0;
        nFluxEvents = 0;
        for (step=1; step<=mcSteps; step++){

            /* output some info */
            if( toSnap == 0 ){
                stats->calcFrameStats( this );
                stats->printStatsSnap();
        
                saveFrameToFile( (char *)restartFileName.c_str() );

                toSnap = mcSnapEvery - 1;
            }
            else{
                toSnap--;
            }

            
            mcSweep();
        }
    }
}


void shi::mcSweep(){

    int i, j, ij, xy, s;
    double flip, energy, newEnergy, boltzVal;
    
    haveClusters=false;
    
    //try some spinflips
    switch( dynamicsType ) {
            case SHI_DYNAMICS_NONCONSERVED:
              
              //average one flip attempt per spin
              xy = xSize * ySize;
              for( s = 0; s < xy; s++ ){
                  //single call to RNG to get both i and j
                  do {
                      flip = rng.ran2();  
                      ij   = int( flip * xy );
                  } while( ij >= xy );
        
                  //integer division: rounds down.
                  j = ( ij / xSize );   
                  i = ij - ( j * xSize );   

                  energy    = siteEnergy( i, j );
                  newEnergy = -1.0 * energy; //by symmetry

                  //test if we flip it back or keep it.
                  boltzVal = exp( -1.0 * ( newEnergy - energy ) );
                  flip = rng.ran2();
                  if ( flip <= boltzVal ) { /*accept it*/
                      FLIP_SPIN( spins[i][j] );  
                  }        
              }

            break;
            case SHI_DYNAMICS_KAWASAKI:
              for( s = 0; s < nUpSpins; s++ ){
               
                do {
                  flip = rng.ran2();   
                  ij   = int( flip * nUpSpins );
                } while( ij >= nUpSpins );
              
                kawasakiFlip( ij );

              }
            break;
            case SHI_DYNAMICS_ASEP:
 
              nHops       = 0;
              nFluxEvents = 0;

              if( asepRightHopP >= 1.0 ){
                //deterministic sweep as in Schuetz, PhysRevE, 1993
                deterministicAsep();
              }else{
              //insert new spins at the left / holes at the right
              if( spins[0][0] == SPIN_DOWN ){
                  flip = rng.ran2();   
                  if( flip < asepInsertP ){
                     spins[0][0]     = SPIN_UP;
                     nUpSpins++;
                     if( nUpSpins == ( 1 + xSize * 0.5 ) ){
                        nFluxEvents = 1;
                     } 
                  }
              }
              if( spins[xSize-1][0] == SPIN_UP ){
                  flip = rng.ran2();   
                  if( flip < asepEscapeP ){
                     spins[xSize-1][0] = SPIN_DOWN;
                     nUpSpins--;
                     if( nUpSpins == ( ( xSize * 0.5 ) - 1) ){
                        nFluxEvents = -1; //let -1 signify a backwards flux... bit sloppy but simple at least.
                     } 
                  }
              }

                //allow spins to move
                for( s = 0; s < xSize; s++ ){
                    do {
                        flip = rng.ran2();   
                        i    = int( flip * xSize );
                    } while( i >= xSize );
                    asepHop( i );
                }
              }

            break;
            default:
                cerr << "error: dynamics type not recognised\n";
                exit( 8 );
    }

    if( shearRate != 0.0 ) {

        // do any shearing that is required
        for( j = ySize - 1; j >= 0; j-- ) {

            flip=rng.ran2();         
            if( flip < shearRate ){
                shear( j );
            }
        }
    }

    return;

}

double shi::siteEnergy( int i, int j ){

    double energy = 0.0;
    int    iWrapped;

    /* neighbours to the left & right */
    if( i > 0 && i < xSize - 1 ){
        energy += PAIR_MATCH( i, j, i - 1, j );
        energy += PAIR_MATCH( i, j, i + 1, j );
    }
    else if( i ==  0 ){ 
        energy += PAIR_MATCH( 0, j, xSize - 1, j );
        energy += PAIR_MATCH( 0, j, 1, j );
    }
    else{ /* i == xSize - 1 */
        energy += PAIR_MATCH( xSize - 1, j, 0, j );
        energy += PAIR_MATCH( xSize - 1, j, xSize - 2, j );
    }

    /* neighbours up and down */
    if( j > 0 && j < ySize - 1 ){
        energy += PAIR_MATCH( i, j, i, j - 1 );
        energy += PAIR_MATCH( i, j, i, j + 1 );
    }
    else if ( j == ySize - 1 ){ 

        iWrapped = i - pbcShear;
        if( iWrapped < 0 ){
               iWrapped += xSize;
        }
        energy  += PAIR_MATCH( i, j, iWrapped, 0 );
        energy  += PAIR_MATCH( i, j, i, j - 1 );
        
    }
    else{ /* j == 0 */
        iWrapped = i + pbcShear;
        if( iWrapped >= xSize ){
            iWrapped -= xSize;
        }
        energy += PAIR_MATCH( i, 0, iWrapped, ySize - 1 );
        energy += PAIR_MATCH( i, 0, i, 1 );
    }

    /* allow for coupling constant */
    energy *= couplingConstant;

    /* external field */
    if( spins[i][j] == SPIN_UP ){
        energy -= fieldStrength;
    }else{
        energy +=  fieldStrength;
    }

       
    return( energy );
}




void shi::shear( int stripIndex ){

    int    i,j, to, from;
    SPIN_T *s;

    s = new SPIN_T[xSize];

    /*maybe should be using a circular buffer to store the spins. */
    for( j = stripIndex; j >= 0; j-- ){ 

        /* store this strip in the workspace s */
        for( i = 0; i < xSize ; i++ ){
            s[i] = spins[i][j];
        }

        for( to = 0; to < xSize; to++ ){
            
            from = to + 1;           //shift from 1 to the right
            while( from >= xSize ){ //wrapping for pbcs
                from -= xSize;
            }
            spins[to][j] = s[from];
        }
    }

    pbcShear++;
    if(  pbcShear >= xSize ){
      pbcShear = 0;
    }

    delete[] s;

}

void shi::asepHop( int spinIndex ){

    double flip;

    //test for "holes" moving left
    if( spinIndex > 0 && spins[spinIndex][0] == SPIN_DOWN ){
        if( spins[spinIndex - 1][0] == SPIN_UP ){
            flip      = rng.ran2();
            if( flip < asepRightHopP ){
                spins[spinIndex][0]     = SPIN_UP;
                spins[spinIndex - 1][0] = SPIN_DOWN;
                nHops++;
            }
        }
    }//test for spins moving right
    else if( spinIndex < xSize - 1 && spins[spinIndex][0] == SPIN_UP ){
        if( spins[spinIndex + 1][0] == SPIN_DOWN ){
            flip      = rng.ran2();
            if( flip < asepRightHopP ){
                spins[spinIndex][0]     = SPIN_DOWN;
                spins[spinIndex + 1][0] = SPIN_UP;
                nHops++;
            }
        }
    }
}

void shi::deterministicAsep(){

    double flip;
    int i;
        

    //first half-sweep
    for( i = 1; i < xSize - 2; i+= 2 ){//missing out the "pair" (xSize-1, 0)
        if( spins[i][0] == SPIN_UP && spins[i+1][0] == SPIN_DOWN ){
            spins[i][0] = SPIN_DOWN;
            spins[i+1][0] = SPIN_UP;
            nHops++;
        }
    }

    //insert new spins at the left / holes at the right
    if( spins[0][0] == SPIN_DOWN ){
         flip = rng.ran2();   
         if( flip < asepInsertP ){
             spins[0][0]     = SPIN_UP;
             nUpSpins++;
             if( nUpSpins == ( 1 + xSize * 0.5 ) ){
                        nFluxEvents = 1;
             } 
         }
    }
    if( spins[xSize-1][0] == SPIN_UP ){
         flip = rng.ran2();   
         if( flip < asepEscapeP ){
             spins[xSize-1][0] = SPIN_DOWN;
             nUpSpins--;
             if( nUpSpins == ( ( xSize * 0.5 ) - 1) ){
                        nFluxEvents = -1; //let -1 signify a backwards flux... bit sloppy but simple at least.
             } 
        }
    }



    //second half-sweep
    for( i = 0; i < xSize - 1; i+= 2 ){
        if( spins[i][0]  == SPIN_UP && spins[i+1][0] == SPIN_DOWN ){
            spins[i][0]   = SPIN_DOWN;
            spins[i+1][0] = SPIN_UP;
            nHops++;
        }
    }

}

void shi::kawasakiFlip( int ij ){

    double flip, energy, newEnergy, boltzVal;
    int    i, j, i2, j2, i3, j3, direction;

    do{
        flip      = rng.ran2();
        direction = int( flip * 4 );
    }while( direction >= 4 );

    i  = upSpinXpos[ij];
    j  = upSpinYpos[ij],
    i2 = i;
    j2 = j;

    switch( direction ){
    case 0:
        j2++;
        if( j2 >= ySize ){
            j2  = 0;
            i2 -= pbcShear;
        }
    break;
    case 1:
        i2++;
        if( i2 >= xSize ){
            i2 = 0;
        }
    break;
    case 2:
        j2--;
        if( j2 < 0 ){
            j2  = ySize - 1;
            i2 += pbcShear;
        }
    break;
    case 3:
        i2--;
        if( i2 < 0 ){
            i2  = xSize - 1;
        }
    break;
    }

    if( spins[i][j] == spins[i2][j2] ){
//        cerr << "null flip " << i << "," << j << " vs " << i2 << "," << j2 << endl; 
        return;
    }

    /* ignore the double-count of the i,j -- i2,j2 interaction,
    ** because it will not show up, counting in this way */
    energy    = 6.0; //start by assuming all interactions bad

    i3        = i;
    j3        = j + 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_UP ) energy -= 2.0;
    j3        = j - 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_UP ) energy -= 2.0;
    i3        = i + 1;
    j3        = j;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_UP ) energy -= 2.0;
    i3        = i - 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_UP ) energy -= 2.0;

  
    i3        = i2;
    j3        = j2 + 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_DOWN ) energy -= 2.0;
    j3        = j2 - 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_DOWN ) energy -= 2.0;
    i3        = i2 + 1;
    j3        = j2;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_DOWN ) energy -= 2.0;
    i3        = i2 - 1;
    wrap_ij( &i3, &j3 );
    if( spins[i3][j3] == SPIN_DOWN ) energy -= 2.0;

    energy *= couplingConstant;
     
    newEnergy = -1.0 * energy; //take advantage of antisymmetry

    //test if we flip it back or keep it.
    boltzVal = exp( ( newEnergy - energy ) * negInvT  );

    //cerr << "testing flip " << i << "," << j << " vs " << i2 << "," << j2 
    //     << " dE = " << (newEnergy - energy) 
    //     << " p = "  << boltzVal << endl; 

    flip = rng.ran2();
    if ( flip <= boltzVal ) { /*accept it*/
        spins[i][j]   = SPIN_DOWN;
        spins[i2][j2] = SPIN_UP;

        upSpinXpos[ij] = i2;
        upSpinYpos[ij] = j2;

    }
    return;
}




























