/*  Monte-Carlo simulation code for statistical physics
    Copyright (C) 2001-2004  Sylvain Reynal
    
    Département de Physique
    Ecole Nationale Supérieure de l'Electronique et de ses Applications (ENSEA)
    6, avenue du Ponceau,    
    F-95014 CERGY CEDEX

    et
    
    Laboratoire de Physique Théorique et Modélisation (LPTM)
    Université de Cergy-Pontoise - Site de Neuville
    F-95031 CERGY CEDEX
    
	
    Tel : 00 +33 130 736 245
    Fax : 00 +33 130 736 667
    e-mail : reynal@ensea.fr
    web page : http://www.ensea.fr/staff/reynal/
*/

package fr.ensea.montecarlo.model;

import fr.ensea.chart.*;
import fr.ensea.math.*;
import fr.ensea.montecarlo.canonical.*;
import fr.ensea.montecarlo.multicanonical.*;
import fr.ensea.montecarlo.data.*;
import java.util.*;

/**
 * This class represents a lattice spin model with nearest-neighbor antiferromagnetic interactions, 
 * and standard periodic boundary conditions. Each spin can take two values, namely +/- 1.
 *
 * This class holds the state of each spin, the lattice energy and the magnetization for each sublattice A and B,
 * from where the order parameter is readily derived.
 */
public class AFIsingLattice implements ILattice2D {

	public int size; 
	public int size2;

	public int[][] spins;

	public int energy; 

	public int magnetizationA, magnetizationB; // magnetization on each sublattice A and B.

	/**
	 * Creates a 2D lattice with 10x10 spins, and random spin values.
	 */
	public AFIsingLattice() {
		init(10);
	}
	
	/**
	 * Creates a 2D lattice with LxL spins, and random spin values.
	 */
	public AFIsingLattice(int L) {
		init(L);
	}

	/**
	 * Reinit the lattice size.
	 * @param L size of the lattice
	 * @param Q N/A here
	 */
	public synchronized void init(int Q, int L){
		init(L);
	}
	
	public synchronized void init(int L){
		size = L;
		size2 = L*L;
		
		spins = new int[size][size];
		createRandomConfiguration(); 
	}
	
	/**
	 * Generates a random configuration
	 */
	public synchronized void createRandomConfiguration(){
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++)
				spins[i][j] = (int)(Math.random() * 2);
		}
		updateEnergy();
		updateMagnetization();
	}
	
	/**
	 * Generates a ground-state configuration.
	 */
	public synchronized void createGroundstateConfiguration(){
		spins = new int[size][size];
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++)
				spins[i][j] = (i+j)%2; // checkerboard
		}
		updateEnergy();
		updateMagnetization();
	}

	/**
	 * Update the magnetization on each sublattice.
	 */
	public synchronized void updateMagnetization(){
		magnetizationA = magnetizationB = 0;
		// sublattice A: even rows
		for(int i=0; i<size; i+=2){
			for(int j=0; j<size; j+=2)
				magnetizationA += spins[i][j]; 
		}
		// sublattice A: odd rows
		for(int i=1; i<size; i+=2){
			for(int j=1; j<size; j+=2)
				magnetizationA += spins[i][j]; 
		}
		// sublattice B: even rows
		for(int i=0; i<size; i+=2){
			for(int j=1; j<size; j+=2)
				magnetizationB += spins[i][j]; 
		}
		// sublattice B: odd rows
		for(int i=1; i<size; i+=2){
			for(int j=0; j<size; j+=2)
				magnetizationB += spins[i][j]; 
		}
	}
	
	/**
	 * Update the (cached) lattice energy.
	 */
	public synchronized void updateEnergy(){
		
		// AF interactions b/w nearest-neighbors:
		//       * - (*)
		//       |
		//      (*)
		//
		// and standard periodic boundary conditions.
		energy = 0;

		int i,j;  
		for (i = 0; i < size; i++) { 
			for (j = 0; j < size; j++) { 
				if (spins[i][j]==spins[i][(j+1)%size]) energy++;
				if (spins[i][j]==spins[(i+1)%size][j]) energy++;
			}
		}
	}
		
	/**
	 * Returns the energy of the ground-state
	 */
	 public double getGroundstateEnergy(){
		 return 0;
	 }


	/////////////////////////////////////////////////////////////////
	//// Accesseurs de propriétés
	/////////////////////////////////////////////////////////////////

	public int getLatticeSize(){
		 return size;
	}
	 
	public int getSpinCount(){
		 return size2;
	}
	
	public int getPottsQ(){
		 return 2;
	}
	 
	public double getEnergy(){
		return energy;
	}

	public double getMagnetization(){
		return Math.abs(2.0*(magnetizationA - magnetizationB)/((double)size2));
	}
	
	public int getSpinValue(int i){
		int col = i%size;
		int row = i/size;
		return spins[row][col];
	}

	public int getSpinValue(int i, int j){
		return spins[i][j];
	}

	/**
	 * Togggle the spin at (i,j) and update the magnetization on each sublattice.
	 */
	public void toggleSpin(int i, int j){
		spins[i][j] = 1 - spins[i][j];
		if (isSublatticeA(i,j)){
			magnetizationA += (2*spins[i][j]-1);
		}
		else {
			magnetizationB += (2*spins[i][j]-1);
		}
	}
	
	/**
	 * Returns true if the given spin belongs to sublattice A
	 * @param i row
	 * @param j column
	 */
	private boolean isSublatticeA(int i, int j){
		return ((i+j)%2) == 0;
	}

	/**
	 * Compute the energy change when the spin at (i,j) is virtually toggled.
	 */
	public int getEnergyChange(int i, int j){

			int dE=0;
			int ancienneValeur = spins[i][j];
			int inn,jnn; // coordonnées voisin

			// voisin à gauche:
			inn=i;jnn=j-1;
			if (jnn<0) jnn += size;
			int valeurVoisin = spins[inn][jnn];
			if (ancienneValeur==valeurVoisin) dE--;
			else dE++;

			// voisin à droite:
			jnn=(j+1)%size;
			valeurVoisin = spins[inn][jnn];
			if (ancienneValeur==valeurVoisin) dE--;
			else dE++;

			// voisin en-dessous:
			inn=(i+1)%size;jnn=j;
			valeurVoisin = spins[inn][jnn];
			if (ancienneValeur==valeurVoisin) dE--;
			else dE++;

			// voisin au-dessus:
			inn=i-1;
			if (inn<0) inn += size;
			valeurVoisin = spins[inn][jnn];
			if (ancienneValeur==valeurVoisin) dE--;
			else dE++;

			return dE;
	}
	
	//////////////////////////////////////
	//// algorithmes
	//////////////////////////////////////
	/**
	 * Créé un algorithme de Metropolis approprié pour ce modèle
	 */
	public Montecarlo createMetropolis(SamplesBag bag, double kT){
		return new AFIsingMetropolis(this, bag, kT);
	}
	
	/**
	 * Créé un algorithme de Wolff approprié pour ce modèle
	 */
	public Montecarlo createWolffCluster(SamplesBag bag, double kT){
		 return null;
	}
	
	public WangLandau createWangLandau(SamplesBag bag, double weight){
		return new AFIsingWL(this,bag,1.0, weight);
	}

	/////////////////////////////////////////////////////////
	//// DEBOGAGE
	/////////////////////////////////////////////////////////
	/**
	 * Retourne une chaîne de caractère informationnelle sur la configuration courant du réseau
	 */
	public String toString(){

		String s = "Modèle d'Ising AF bidimensionnel  : \n";
		s += "* size de réseau=" + Integer.toString(size) + "\n";
		s += "* M=" + getMagnetization() + "\n";
		s += "* E=" + getEnergy() + "\n";
		// affiche le contenu de chaque ligne du réseau :
		for (int row = 0; row < size; row++){
			for(int col = 0; col < size; col++){
				s += Integer.toString(spins[row][col]) + " ";
			}
			s += "\n";
		}
		return s;
	}
	
	/**
	 *
	 */
	public static void main(String[] args){
		AFIsingLattice lat = new AFIsingLattice(4);
		lat.createGroundstateConfiguration();
		/*
		for (int i=0; i<lat.size; i++){
			for (int j=0; j<lat.size; j++){
				lat.toggleSpin(i,j);
			}
		}*/
		lat.updateEnergy();
		System.out.println(lat);
	}
	

}

