//=======================================================================
// Metropolis+Wolff Monte Carlo simulation of Classical Heisenberg Model
// on Arbitrary Network with Single-Site Anisotropy
//=======================================================================
#include <cmath>      // for M_PI
#include <cassert>
#include <string>			// for string
#include <iostream>		// for cout
#include <iomanip>		// for setf
#include <sstream>		// for ostringstream
#include <fstream>		// for ofstream and ifstream
#include <vector>			// for std::vector
#include <unistd.h> 	// for usleep
#include <climits>		// for INT_MAX
#include <chrono>			// for chrono
#include <fstream> // for ifstream
#include <GL/glew.h>
#include <GLFW/glfw3.h> 
#include <glm/glm.hpp>  // we shamelessly use glm::vec3 to represent spins
//#include <glm/gtc/matrix_transform.hpp>
#include <chrono> // for std::chrono
#include <random> // for random numbers
#include "utils.hh" // for homemade utils
#include "gui.hh" // for homemade graphics stuff

typedef float flo;
using std::cout; using std::cerr; using std::endl; using std::ostream; using std::setw; using std::string;
using std::cout; using std::cerr; using std::cin; using std::endl; using std::setw;
using std::string; using std::ostringstream; using std::ofstream; using std::ifstream; using std::istringstream;
using std::min; using std::max; using std::vector;


using glm::vec2; using glm::vec3; using glm::vec4; using std::ifstream;
using glm::mat3; using glm::mat4;





//########################################
// 		SIMULATION VARIABLES AND CODE
//########################################
typedef int atom;                                       // represent each atom by an integer index
typedef int bond;
typedef int neigh;
typedef int dir;
bool useGUI = true;

int spinType = 3;     // Heisenberg by default
double T = 0.001;     // temperature
double easyAxisAniso = 0.;  // K parameter
//-------- Parameters read from mclattice.dat (POSCAR)
string materialName;
double scaleFactor;
array2<double> basisVectors (3,3);
vector<string> elementNames;
vector<int> elementCounts;
string coordType;
array2<double> cad;  // fractional coordinate c of atom a in direction d
array2<double> rad;  // Cartesian coordinate c of atom a in direction d
//-------- Parameters read from mcbonds.dat
vector<int> ib,jb,ub,vb,wb,bb;
//-------- Derived parameters
atom amax;
bond bmax;
dir dmax;
neigh nmax;               // maximum number of neighbors of any atom
std::vector<vec3> sa;     // spin at atom a
std::vector<flo> Ka;      // easy-axis anisotropy parameter at atom a
std::vector<flo> Jbtype;  // coupling as a function of bond type
//std::vector<flo> Jb;    // coupling as a function of bond index -- see "coupling" below
array2<flo> Jan;     // exchange coupling J from atom n to neighbor n
array2<atom> aan;    // atom a that is the nth neighbor of atom a 

int stepsTuning = 50000000;			// max number of steps for tuning and equilibration
int stepsCollect = 50000000;			 // max number of steps for collecting statistics
int stepsStats = 10;					 // interval (in MC steps) between stats collection
float timeTuning = 6;   // max walltime (in seconds) for tuning and equilibration
float timeCollect = 10; // max walltime (in seconds) for collecting statistics
float metropStepsize = 1.0;	// default stepsize (overwritten during tuning)
int wolffClusters = 1;       // default Wolff-to-Metropolis ratio (overwritten during tuning)


atom randomAtom ()                      { return irand(amax);    }
neigh neighbors (atom a)                { return nmax;           }
atom neighbor (atom a, neigh n)         {	return aan(a,n);       }
flo position (atom a, dir d)            { return rad(a,d);       }

flo coupling (bond b)            { return Jbtype[bb[b]];       }



//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// SOME SPIN UTILITIES
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
vec2 randomXY () {
	flo vv;
	vec2 u;
	do {
		u.x = frand() - .5f;
		u.y = frand() - .5f;
		vv = u.x*u.x + u.y*u.y;
	}
	while (vv>.25 || vv<1e-8);
	flo oovv = 1/sqrt(vv);
	u.x *= oovv;
	u.y *= oovv;
	return u;
}
vec3 flipXY (vec3 s, vec2 u){
	vec3 sNew;
	double us = u.x * s.x + u.y * s.y;
	sNew.x = s.x - 2*us*u.x;    sNew.y = s.y - 2*us*u.y;  sNew.z = s.z;
	return sNew;
}
vec3 flipZ (vec3 s) {return vec3 (s.x, s.y, -s.z); }

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// ALGORITHM: METROPOLIS
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
int nAccept=0,nReject=0;
void metrop (atom a, float stepSize) {
	if (spinType==1) {   // ISING SPINS
		
		//======== Propose to flip along z axis
		vec3 sOld = sa[a];
		vec3 sNew = flipZ (sOld);
		//======== Calculate energy difference where E = -J Si . Sj + K sin^2 theta
		//======== We can write Eaniso = -K S_z^2 up to an additive constant
		vec3 Beff (0., 0., 0.);
		for (neigh n=0; n<nmax; ++n) {
			atom a2 = neighbor (a, n);
			if (a2 < 0) continue; // negative atom indices indicate NO neighbor
			Beff += Jan(a,n) * sa[a2]; // B_a += J_an * S_a2
		}
		float EOld = - Ka[a] * sqr(sOld.z) - dot (Beff, sOld);
		float ENew = - Ka[a] * sqr(sNew.z) - dot (Beff, sNew);
		float EChg = (ENew - EOld)/T;
		//======== Apply Metropolis
		bool accept;
		if (EChg <= 0) accept = true;
		else if (EChg > 40) accept = false;
		else if (frand() < exp(-EChg)) accept = true;
		else accept = false;
		if (accept) { nAccept++; sa[a] = sNew; }
		else        { nReject++;               }
		
		
	}
	else if (spinType==3) {		// HEISENBERG SPINS
		//======== Propose move
		vec3 sOld = sa[a];
		vec3 sNew = normalize (sOld + stepSize * vec3 (frandNormal(),frandNormal(),frandNormal()));
		//======== Calculate energy difference where E = -J Si . Sj + K sin^2 theta
		//======== We can write Eaniso = -K S_z^2 up to an additive constant
		vec3 Beff (0., 0., 0.);
		for (neigh n=0; n<nmax; ++n) {
			atom a2 = neighbor (a, n);
			if (a2 < 0) continue; // negative atom indices indicate NO neighbor
			Beff += Jan(a,n) * sa[a2]; // B_a += J_an * S_a2
		}
		float EOld = - Ka[a] * sqr(sOld.z) - dot (Beff, sOld);
		float ENew = - Ka[a] * sqr(sNew.z) - dot (Beff, sNew);
		float EChg = (ENew - EOld)/T;
		//======== Apply Metropolis
		bool accept;
		if (EChg <= 0) accept = true;
		else if (EChg > 40) accept = false;
		else if (frand() < exp(-EChg)) accept = true;
		else accept = false;
		if (accept) { nAccept++; sa[a] = sNew; }
		else        { nReject++;               }
	}
}
void metrop (float stepSize) { // perform a sweep across the whole lattice
	for (atom a=0; a<amax; ++a) metrop (a, stepSize);
}

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// ALGORITHM: WOLFF
// TBD: MAKE INTO CLASSES
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//======== Cluster data structure ========
typedef int mctime;
mctime tCurrent = 1;
std::vector<mctime> tTouched;
bool inCluster (atom a)    {    return tTouched[a]==tCurrent;    }
void addToCluster (atom a) {    tTouched[a] = tCurrent;    }
void clearCluster () {
	tCurrent -= 1;
	if (tCurrent==0) {
		tCurrent = INT_MAX-1;
		for (atom a=0; a<amax; a++) tTouched[a] = -1;      // explicitly clear cluster flags
	}
}
//======== Queue data structure ========
int lhead,ltail;
std::vector<atom> iqueue;
void clearQueue ()          {    lhead = ltail = -1;         }
void addToQueue (atom a)    {    iqueue[++ltail] = a;        }
atom getFromQueue ()        {    return iqueue[++lhead];     }
bool queueNotEmpty ()       {    return lhead<ltail;         }
int queueThroughput ()      {    return ltail+1;             }   // number of elements that have been processed
float bondWeight (float BJ, vec3 s1, vec3 s2)    {    return exp (BJ*dot(s1,s2));    }
//======== Wolff algorithm itself ========
void wolffInit (int amax) {
	tTouched.resize (amax);
	iqueue.resize (amax);
}
void wolffZ () {
	clearCluster ();
	clearQueue ();
	atom a2 = randomAtom ();                        // choose seed atom
	addToCluster (a2);
	addToQueue (a2);
	while (queueNotEmpty()) {
		atom a1 = getFromQueue ();                   // atom a1 is INSIDE the cluster
		vec3 s1 = sa[a1];                             // read s_1
		vec3 s1p = flipZ (s1);                       // find s'_1        
		for (int n=0; n<neighbors(a1); n++) {
			a2 = neighbor (a1, n);                     // let a2 be a neighbor of atom a1            
			if (!inCluster(a2)) {                      // if atom a2 is OUTSIDE the cluster                
				vec3 s2 = sa[a2];                    // read s_2
				//spin s2p = flip (s2, axis);            // find s'_2 (not necessary)       
				float BJ = Jan(a1,n)/T;
				float ws1s2 = bondWeight (BJ, s1, s2);   // find w(s_1, s_2)							
				float ws1ps2 = bondWeight (BJ, s1p, s2); // find w(s'_1, s_2)                
				if (drand() >= ws1ps2 / ws1s2) {         // find pstop = min (1, w(s'_1,s_2) / w(s_1,s_2))
					addToCluster (a2);                     // with probability 1 - pstop
					addToQueue (a2);
				}
			}
		}
		sa[a1] = s1p;                              // commit the new value of s'_1 to the spin configuration
		// Actually it is ok to do this when calculating s1p above
	}
}
void wolffXY () {
	clearCluster ();
	clearQueue ();
	atom a2 = randomAtom ();                        // choose seed atom
	addToCluster (a2);
	addToQueue (a2);
	vec2 u = randomXY ();
	while (queueNotEmpty()) {
		atom a1 = getFromQueue ();                   // atom a1 is INSIDE the cluster
		vec3 s1 = sa[a1];                          // read s_1
		vec3 s1p = flipXY (s1, u);                       // find s'_1        
		for (int n=0; n<neighbors(a1); n++) {
			a2 = neighbor (a1, n);                     // let a2 be a neighbor of atom a1            
			if (!inCluster(a2)) {                      // if atom a2 is OUTSIDE the cluster                
				vec3 s2 = sa[a2];                    // read s_2
				//spin s2p = flip (s2, axis);            // find s'_2 (not necessary)                
				float BJ = Jan(a1,n)/T;
				float ws1s2 = bondWeight (BJ, s1, s2);   // find w(s_1, s_2)							
				float ws1ps2 = bondWeight (BJ, s1p, s2); // find w(s'_1, s_2)                
				if (drand() >= ws1ps2 / ws1s2) {         // find pstop = min (1, w(s'_1,s_2) / w(s_1,s_2))
					addToCluster (a2);                     // with probability 1 - pstop
					addToQueue (a2);
				}
			}
		}
		sa[a1] = s1p;                              // commit the new value of s'_1 to the spin configuration
		// Actually it is ok to do this when calculating s1p above
	}
}

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// ESTIMATOR: MAGNETIZATION
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
vec3 findMag () {
	vec3 mmm (0,0,0);
	for (int a=0; a<amax; ++a) mmm += sa[a];
	return mmm;
}

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// GUI CODE SPECIFIC TO THE PRESENT APPLICATION
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
GUI gui; 
Model model1; // static objects in scene (coord axes, unit cell, etc.)
Model model2; // dynamic objects in scene 

int speed = 11;
int frame = 0;

void guiInit () {
	if (!useGUI) return;
	cout <<
	"\n======== KEYBOARD CONTROLS ========\n"
	"W A S D  Q Z   Up Down Left Right:  Control camera \n"
	"9 0                              :  Lower or raise temperature \n"
	"Space                            :  Pause/resume (poorly implemented) \n"
	"Escape                           :  Quit\n\n";
	//======== Set up the GUI object, which will handle most of the rendering stuff
	gui.windowWidth = 900;                  // this must be done first
	gui.windowHeight = 600;
	gui.setup ();	                          // call the actual OpenGL/GLFW window setup commands
	float xmin,ymin,xmax=-1.e30,ymax=-1.e30;
	xmin = 0.;	// shoudl imporve
	ymin = 0.;
	for (atom a=0; a<amax; ++a) {
		xmax = std::max(xmax, position(a,0));
		ymax = std::max(ymax, position(a,1));
	}
	gui.camPos   = vec3 (0.25*xmax, -20., 100.0);      // camera position (HARDWIRED!)
	gui.camTheta = 140 * M_PI/180;          // camera pitch (90 degrees means straight ahead)
	gui.camPhi   = 90 * M_PI/180;           // camera yaw (0 degrees means looking along +x)
	gui.lightPos   = 100.f * vec3 (0,0,1);   // light position in world coordinates
	gui.lightColor = 4.0f*sqr(100.f) * vec3 (1,1,1);
	const GLfloat l = 10.0; // length of axis symbols
	const GLfloat r = 1.0; // radius of axis cones
	model1.color (1,0,0); model1.cone (0,0,0, l,0,0, r, 12); // 12-segment cone
	model1.color (0,1,0); model1.cone (0,0,0, 0,l,0, r, 12);
	model1.color (0,0,1); model1.cone (0,0,0, 0,0,l, r, 12);
	model1.uploadToGPU ();
}

void guiUpdate () {
	if (!useGUI) return;
	//======== Re-render mobiles to buffers on CPU; transfer data to vertexXXX buffers on GPU
	const GLfloat l = 4; // length of cones representing spins
	const GLfloat r = 1; // radius of cones representing spins
	model2.clear ();       
	for (atom a=0; a<amax; ++a) {
		float X = position (a, 0);
		float Y = position (a, 1);
		float Z = position (a, 2);
		vec3 &s = sa[a];
		model2.color (.5-.5*s.z, .5+.5*s.z, .9-.8*fabs(s.z)); // up is green, down is red
		model2.cone (X, Y, Z, X+s.x*l, Y+s.y*l, Z+s.z*l,    r, 12);
	}
	model2.uploadToGPU ();
	//======== Tell GPU to render from GPU's vertexXXX buffers to the framebuffer
	gui.clear ();
	gui.render (model1);		
	gui.render (model2);
	//======== Finally swap the offscreen buffer with the onscreen buffer
	gui.swapBuffers ();
	//======== Handle mouse and keyboard input
	gui.respond ();
	if (glfwGetKey (gui.window, GLFW_KEY_9) == GLFW_PRESS) {
		T -= 0.01; if (T<0.01) T=0.01;
		cout << "T = " << T << endl;
	}
	if (glfwGetKey (gui.window, GLFW_KEY_0) == GLFW_PRESS) {
		T += 0.01; 
		cout << "T = " << T << endl;
	}
	if (glfwGetKey (gui.window, GLFW_KEY_SPACE) == GLFW_PRESS) sleep (1);
	if (gui.shouldExit) exit(0);				
}






//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// readPOSCAR ("mclattice.dat")
// This sets global variables, most importantly:
// amax, cad, basisVectors, rad
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
void readPOSCAR (string filename) {
	ifstream f (filename); // could be POSCAR
	materialName = readLine (f);                 // read string
	istringstream(readLine(f)) >> scaleFactor;   // read first number
	for (int i=0; i<3; ++i) {                    // for the next 3 lines
		istringstream(readLine(f))  								//read first 3 numbers
		>> basisVectors(i,0) >> basisVectors(i,1) >> basisVectors(i,2);
	}
	{
		auto iss = istringstream(readLine(f));
		while (true) {                    // read all strings on line
			string s; iss >> s; if (iss.fail()) break; elementNames.push_back (s);
		}
	}
	{
		auto iss = istringstream(readLine(f));
		while (true) {                    // read numbers
			int i; iss >> i; if (iss.fail()) break; elementCounts.push_back (i);
		}
	}
	coordType = readLine (f);   // should be "DIRECT"
	amax = 0; for (auto& n: elementCounts) amax += n;		//THIS IS GLOBAL
	cad.resize (amax, 3);
	for (int a=0; a<amax; ++a) {
		istringstream(readLine(f))  								//read first 3 numbers
		>> cad(a,0) >> cad(a,1) >> cad(a,2);
	}
	rad.resize (amax, 3);
	for (int a=0; a<amax; ++a) {
		for (int i=0; i<3; ++i) {
			rad(a,i) = 0.;
			for (int j=0; j<3; ++j) {
				rad(a,i) += cad(a,j)*basisVectors(j,i);
			}
		}
	}
	// 		cout << materialName << "\n";
	// 		cout << scaleFactor << "\n";
	// 		cout << basisVectors << "\n";
	// 		for (auto s:elementNames) cout << s << "\n";
	// 		for (auto s:elementCounts) cout << s << "\n";
	cout << "Number of atoms, amax = " << amax << "\n";	
}

void readBonds (string filename) {
	ifstream f (filename); // could be POSCAR
	int b = 0;
	while (true) {
		string s = readLine (f);
		if (f.fail()) break;
		// should   ltrim (s);   and strip comment lines
		int i,j,u,v,w,b;
		istringstream(s) >> i >> j >> u >> v >> w >> b;
		ib.push_back (i);
		jb.push_back (j);
		ub.push_back (u);
		vb.push_back (v);
		wb.push_back (w);
		bb.push_back (b);
	}
	bmax = ib.size();
	// convert atom indices and bond indices to be zero-based
	for (bond b=0; b<bmax; ++b) {
		--ib[b];	--jb[b];	--bb[b];
	}
}
void buildNeighborLists () {
	//-------- COUNT NEIGHBORS OF EACH ATOM
	vector<int> nmaxa (amax);
	for (int a=0; a<amax; ++a) nmaxa[a]=0;	
	for (int b=0; b<bmax; ++b) {
		++nmaxa[ib[b]];
		++nmaxa[jb[b]];
	}
	nmax = max (nmaxa); // GLOBAL!!!
	//-------- NOW ALLOCATE ARRAYS
	Jan.resize (amax, nmax); // coupling from atom a to neighbors n
	aan.resize (amax, nmax); // atom that is atom a's  nth neighbo
	//-------- CONSTRUCT NEIGHBORLIST OF EACH ATOM
	for (int a=0; a<amax; ++a) {
		for (int n=0; n<nmax; ++n) {
			aan(a,n) = -1;  // foolproofing
		}
	}
	
	for (int a=0; a<amax; ++a) nmaxa[a]=0;	
	for (int b=0; b<bmax; ++b) {
		int i = ib[b];
		int j = jb[b];
		aan(i,nmaxa[i]) = j;
		aan(j,nmaxa[j]) = i;
		Jan(i,nmaxa[i]) = coupling (b);
		Jan(j,nmaxa[j]) = coupling (b);
		++nmaxa[i];  // increment AFTER
		++nmaxa[j];
	}
	cout << "Max number of neighbors of any atom, nmax = " << nmax << endl;
}

//=======================================================================
//  MAIN
//=======================================================================
int main () {
	
	cout << "-------- Reading pars.dat --------\n";
	string fileName ("pars.dat");
	readPar (fileName, "useGUI", useGUI);
	readPar (fileName, "temperature", T);
	readPar (fileName, "spinType", spinType);
	readPar (fileName, "stepsTuning",  stepsTuning);
	readPar (fileName, "stepsCollect", stepsCollect);
	readPar (fileName, "timeTuning",  timeTuning);
	readPar (fileName, "timeCollect", timeCollect);
	if (spinType==3) {
		readPar (fileName, "metropStepsize", metropStepsize);
	}
	readPar (fileName, "easyAxisAniso", easyAxisAniso);
	
	cout << "-------- Reading mclattice.dat (POSCAR format) --------\n";
	readPOSCAR ("mclattice.dat");
	cout << "-------- Reading mcbonds.dat --------\n";
	readBonds ("mcbonds.dat");
	cout << "Number of bonds, bmax = " << bmax << "\n";
	cout << "-------- Reading Jb.dat --------\n";
	int bondTypes = 1 + *max_element(bb.begin(), bb.end()); // max element in bb
	readArray ("Jb.dat", Jbtype, bondTypes);
	
	Ka.resize (amax);
	for (atom a=0; a<amax; ++a)  Ka[a] = easyAxisAniso;
	
	cout << "-------- Populating Jan and aan based on ib,jb,ub,vb,wb,bb, and Jb --------\n";
	buildNeighborLists ();
	
	//======== Init simulation variables ========
	sa.resize (amax);
	for (atom a=0; a<amax; ++a) {
		sa[a] = vec3 (0,0,1); // ordered setup
		//sa[a] = normalize (vec3 (frandNormal(),frandNormal(), frandNormal())); // disordered setup
		// If you have Ising spins, then please don't start in a 3D disordered state
	}
	wolffInit (amax);
	
	if (stepsCollect==0) {
		//=======================================================================
		//  MICKEY MOUSE BRANCH
		//=======================================================================
		
		//======== If stepsCollect=0, then do a demo for animation purposes ========
		guiInit ();
		sw1.tic ();
		int nstep;
		for (nstep=0; nstep<stepsTuning; ++nstep) {
			metrop (metropStepsize);			
			if (nstep%1 == 0) guiUpdate ();
			sw1.toc(); if (sw1.timeElapsed >= timeTuning) break;
		}
	}
	else {
		
		//=======================================================================
		//  SERIOUS BRANCH
		//=======================================================================
		//======== Auto-tune Metropolis step size ========
		float fAccept;
		sw1.tic ();	
		for (int nstep=0; nstep<stepsTuning/3; ++nstep) {		
			::nAccept = 0;
			::nReject = 0;
			metrop (metropStepsize);   // Metropolis sweep over entire lattice
			wolffXY ();
			wolffZ ();
			fAccept = float(nAccept) / (nAccept + nReject);
			if (fAccept > .5) {metropStepsize *= 1.01; metropStepsize = std::min(metropStepsize,1.f);}
			else              metropStepsize *= 0.99;
			sw1.toc(); if (sw1.timeElapsed >= timeTuning/3) break;
		}
		cout << "Metropolis stepsize                          = " << metropStepsize << endl;
		cout << "Metropolis acceptance ratio (final)          = " << fAccept << endl;
		//======== Auto-tune Metropolis-to-Wolff ratio ========
		sw1.tic ();	
		int wolffFlips = 0;
		int count = 0;
		for (int nstep=0; nstep<stepsTuning/3; ++nstep) {
			metrop (metropStepsize);
			wolffXY ();	wolffFlips += queueThroughput();
			wolffZ ();	wolffFlips += queueThroughput();
			++count;
			sw1.toc(); if (sw1.timeElapsed >= timeTuning/3) break;
		}
		
		float wolffClusterSize = wolffFlips / count;
		wolffClusters = floor(amax/wolffClusterSize); 
		if (wolffClusters%2 == 0) wolffClusters += 1; // want an odd integer
		cout << "Wolff cluster size (average)                 = " << wolffClusterSize << endl;
		cout << "Wolff clusters to build per Metropolis sweep = " << wolffClusters << endl;
		//======== Equilibrate without adjusting settings ========
		sw1.tic ();	
		for (int nstep=0; nstep<stepsTuning/3; ++nstep) {
			metrop (metropStepsize);
			wolffXY ();
			wolffZ ();
			sw1.toc(); if (sw1.timeElapsed >= timeTuning/3) break;
		}
		
		//======== Collect data ========
		Recorder recorder;	
		TReblocker reblocker;
		guiInit ();
		sw1.tic ();
		int nstep;
		for (nstep=0; nstep<stepsCollect; ++nstep) {
			metrop (metropStepsize);
			
			for (int iter=0; iter<wolffClusters; ++iter) {
				wolffXY ();
				wolffZ ();
			}
			
			if (nstep%stepsStats == 0) {
				vec3 mag = findMag ();
				float Mx = mag.x / amax; // normalize by number of lattice sites
				float My = mag.y / amax;
				float Mz = mag.z / amax;
				float Mx2 = sqr (Mx);
				float My2 = sqr (My);
				float Mz2 = sqr (Mz);
				float Mx4 = sqr (Mx2);
				float My4 = sqr (My2);
				float Mz4 = sqr (Mz2);
				float M2 = Mx2 + My2 + Mz2;
				float M4 = sqr (M2);
				float Mclust = queueThroughput ();
				std::vector<float> stats {1.0f*nstep, M2, M4, Mclust, Mx, My, Mz, Mx2, My2, Mz2, Mx4, My4, Mz4};
				reblocker.add (&stats[0], stats.size());
				//recorder.add (stats);  //write to the history file
			}
			
			if (nstep%10 == 0) guiUpdate ();
			
			sw1.toc(); if (sw1.timeElapsed >= timeCollect) break;
		}
		
		
		cout << "Walltime elapsed (secs)               = " << sw1.timeElapsed << endl;
		cout << "Total MC moves during collection      = " << nstep << endl;
		cout << "Total stats samples during collection = " << reblocker.nmax << endl;
		cout << "================== Statistics ================\n";
		float *statsAvg=NULL, *statsErr=NULL, *statsTau=NULL;
		reblocker.report (statsAvg, statsErr, statsTau);
		std::ofstream ofsStats ("stats.dat");
		ofsStats << std::setprecision(6);
		for (int i=0; i<reblocker.imax; ++i) {
			ofsStats
			<< setw(14) << statsAvg[i]
			<< setw(14) << statsErr[i]
			<< setw(14) << statsTau[i]
			<< endl;
		}
		ofsStats << endl;
		delete[] statsAvg;
		delete[] statsErr;
		delete[] statsTau;
	}
	
	return 0;
}
//cout << std::fixed << std::setprecision(4);


