Jump to content

Rep:Mod:explosivesheep:Scripts

From ChemWiki

IsingLattice.py (unoptimized)

import numpy as np

class IsingLattice:

	E = 0.0
	E2 = 0.0
	M = 0.0
	M2 = 0.0

	n_cycles = 0

	def __init__(self, n_rows, n_cols):
		self.n_rows = n_rows
		self.n_cols = n_cols
		self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))

	def energy(self):
		"Return the total energy of the current lattice configuration."
		energy = 0.0
		for i in len(self.lattice) - 1: #loop through indexes of all rows
			row = self.lattice[i]
			for j in len(row) - 1: #loop through indexes of all columns
				spin = self.lattice[i][j] #spin at the current position
				energy += spin * self.lattice[i][j-1] #multiply with its neighbour to the left
				# at the leftmost edge case, [i][-1] is the rightmost spin

				energy += spin * self.lattice[i-1][j] #multiply with its neighbour above
				# at the top edge case, [i-1][j] is the bottom spin

				# only multiply in one direction to negate the half factor
				# also preventing [j+1] or [i+1] which returns index errors
		return energy

	def magnetisation(self):
		"Return the total magnetisation of the current lattice configuration."
		magnetisation = 0
		for i in len(self.lattice) - 1: #loop through indexes of all rows
			for j in len(self.lattice[i]) - 1: #loop through indexes of all columns
				magnetisation += self.lattice[i][j]
				# loop through each spin and add to magnetisation
		return magnetisation

	def montecarlostep(self, T):
		# complete this function so that it performs a single Monte Carlo step
		# 1
		E_0 = self.energy()
	
		# 2 the following two lines will select the coordinates of the random spin for you
		random_i = np.random.choice(range(0, self.n_rows))
		random_j = np.random.choice(range(0, self.n_cols))
		self.lattice[random_i][random_j] = - self.lattice[random_i][random_j] #flips a spin at a random position
		
		# 3 calculate the energy difference
		delta_E = self.energy() - E_0
		print(delta_E)
		
		# 4 decide if the new config should be rejected
		if delta_E > 0.0: #if >0, coin flip; else do nothing and keep new configuration
			random_number = np.random.random() #choose a random %runnumber in the range[0,1) for you
			boltzmann_dist = np.exp(-delta_E/T)

			if random_number > boltzmann_dist: #if R>dist, reject new configuration; else do nothing and keep new configuration
				self.lattice[random_i][random_j] = - self.lattice[random_i][random_j] #reject new configuration by reversing the spin flip
		
		# 5 keep a sum of energies etc
		self.E += self.energy()
		self.E2 += self.energy()**2
		self.M += self.magnetisation()
		self.M2 += self.magnetisation()**2
		self.n_cycles += 1
		
		return self.energy(), self.magnetisation()

	def statistics(self):
		# complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them
		return self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles

Note: The ILanim.py provided only took 4 values from self.statistics(), omitting the number of cycles. But later on in other scripts given to us, a fifth value, n_cycles, had to be returned. The following optimized IsingLattice.py was changed based on this fact.

IsingLattice.py (optimized)

import numpy as np

class IsingLattice:

	E = 0.0
	E2 = 0.0
	M = 0.0
	M2 = 0.0

	n_cycles = 0
	equilibrium = 0

	def __init__(self, n_rows, n_cols):
		self.n_rows = n_rows
		self.n_cols = n_cols
		self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))
		self.equilibrium = n_rows * n_cols * 25
		#scale the equilibrium cutoff cycle according to the number of spins

	def energy(self):
		"Return the total energy of the current lattice configuration."
		energy = 0.0

		#shift the spins by moving down individual rows
		roll_row = np.roll(self.lattice, 1, axis=1) #axis 1 is a list of rows
		# the bottom row will be shifted to become the top row, satisfying periodic boundary conditions
		
		#shift the spins by moving individual columns to the right
		roll_col = np.roll(self.lattice, 1, axis=0) #axis 0 is a list of spins in a column
		# the rightmost spin will be shifted to become the leftmost spin, satisfying periodic boundary conditions
		
		#multiply the spins and sum the result
		#the 1/2 factor is negated as we avoided double counting by rolling in one direction only
		energy += -np.sum(np.multiply(self.lattice, roll_row)) #multiply neighbours' spins in horizontal direction and sum the energies
		energy += -np.sum(np.multiply(self.lattice, roll_col)) #multiply neighbours' spins in vertical direction and sum the energies
		return energy

	def magnetisation(self):
		"Return the total magnetisation of the current lattice configuration."
		return np.sum(self.lattice) #total magnetisation is sum of all spins in the lattice

	def montecarlostep(self, T):
		# complete this function so that it performs a single Monte Carlo step
		# Step 1: store the original energy
		E_0 = self.energy()
		
		# Step 2: the following two lines will select the coordinates of the random spin for you
		random_i = np.random.choice(range(0, self.n_rows))
		random_j = np.random.choice(range(0, self.n_cols))
		self.lattice[random_i][random_j] = - self.lattice[random_i][random_j] #apply the spin flip at random i,j
		
		# Step 3: calculate the energy difference
		delta_E = self.energy() - E_0 #energies are in units of Boltzmann constant
		# print(delta_E)
		
		# Step 4: decide if the new config should be rejected
		if delta_E > 0: #if spin is energetically unfavored, coin flip; else do nothing and keep new config
			random_number = np.random.random() #choose a random %runnumber in the range[0,1)
			exponential = np.exp(-delta_E/T) #calculates likelihood of spin flip by boltzmann function
			#if random number > boltmann distribution, reject new config; else do nothing and keep new config
			if random_number > exponential: 
				self.lattice[random_i][random_j] = -self.lattice[random_i][random_j] #reverse the spin flip
		
		# 5 keep a sum of energies etc. after equilibrium state is reached
		self.n_cycles += 1
		E = self.energy()
		M = self.magnetisation()

		if self.n_cycles > self.equilibrium: #check if it has reached a predetermined number of cycles
			self.E += E
			self.E2 += E**2
			self.M += M
			self.M2 += M**2
		
		return E, M

	def statistics(self):
		# complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them
		n = self.n_cycles - self.equilibrium
		return self.E/n, self.E2/n, self.M/n, self.M2/n, self.n_cycles

ILtemperaturerange.py

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

# Defining the Ising lattice
  n_rows = 8
  n_cols = 8
  il = IsingLattice(n_rows, n_cols)
  il.lattice = np.ones((n_rows, n_cols))
  spins = n_rows*n_cols
  
  # Setting the number of Monte Carlo iterations
runtime = 150000
times = range(runtime)

# Setting the temperature range
  temps = np.arange(1.5, 5.0, 0.05) #start, end, increment
  
  # Lists to store statistics
  energies = []
  magnetisations = []
  energysq = []
  magnetisationsq = []
  
  for t in temps:
      for i in times:
          if i % 1000 == 0:
              print(str(i)+"th iteration at "+str(t)+"K")
        energy, magnetisation = il.montecarlostep(t)
    aveE, aveE2, aveM, aveM2, n_cycles = il.statistics()
    energies.append(aveE)
      energysq.append(aveE2)
  
      magnetisations.append(aveM)
      magnetisationsq.append(aveM2)
      #reset the IL object for the next cycle
    il.E = 0.0
      il.E2 = 0.0
      il.M = 0.0
      il.M2 = 0.0
      il.n_cycles = 0
  
  #Plot
  fig = pl.figure()
  enerax = fig.add_subplot(2,1,1) #y-axis on top figure
  enerax.set_ylabel("Energy per spin")
  enerax.set_xlabel("Temperature")
  enerax.set_ylim([-2.1, 2.1])
  magax = fig.add_subplot(2,1,2) #y-axis on bottom figure
  magax.set_ylabel("Magnetisation per spin")
  magax.set_xlabel("Temperature")
  magax.set_ylim([-1.1, 1.1])
  enerax.plot(temps, np.array(energies)/spins)
  magax.plot(temps, np.array(magnetisations)/spins)
  
  #Calculate errors: std error = square root of (variance/number of cycles) where variance = <E2> - <E>**2
energyerrors = np.sqrt((np.array(energysq)-np.array(energies)**2)/n_cycles)
magnetisationerrors = np.sqrt((np.array(magnetisationsq)-np.array(magnetisations)**2)/n_cycles)

#Plot error bars
  enerax.errorbar(temps, np.array(energies)/spins, yerr=energyerrors/2, linestyle='o') #energy error bars
 magax.errorbar(temps, np.array(magnetisations)/spins, yerr=magnetisationerrors/2, linestyle='o') #mag error bars
 
  pl.show()
  
  final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))
 file_name = str(n_rows) + "x" + str(n_cols) + ".dat" #dynamic file name
 np.savetxt(file_name, final_data)

IL.finalframe.py (modified)

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

#Initiate plot
 figure = pl.figure()
 subplotnumber = 0
 
 sizes = [7, 16, 32]
 #original setting is 7
 #16 and 32 used to find out equilibrium steps for larger system sizes
temps = [1, 2.69, 5]#temperatures set to below, around and above curie temperature
runtime = 50000 #should be enough to find equilibrium step, not interested in data atm

for temperature in temps:
	for n in sizes:
		il = IsingLattice(n,n)
		spins = n**2

		#subplot settings
		subplotnumber += 1
		# print(subplotnumber)
		cols = len(sizes) #
		rows = len(temps) * 3

		#Monte Carlo Simulation
		times = range(runtime)
		E=[]
		M=[]
		for i in times:
			if i %1000 == 0:
				print("Step: ", i, " Size:", n, " Temp: ", temperature)
			energy, magnetisation = il.montecarlostep(temperature)
			E.append(energy)
			M.append(magnetisation)

		#diagram subplot
		diag = figure.add_subplot(rows,cols,subplotnumber)
		diag.matshow(il.lattice)
		diag.tick_params(axis='both', which='major', labelsize=8)
		subplotnumber += cols
		# print(subplotnumber)

		#energy subplot
		enerax = figure.add_subplot(rows,cols,subplotnumber)
		enerax.set_ylabel(r"Energy per spin ($k_B$ Joules)", fontsize=8)
		enerax.set_xlabel("Monte Carlo Steps", fontsize=8)
		enerax.tick_params(axis='both', which='major', labelsize=8)
		enerax.plot(times, np.array(E)/spins)
		enerax.set_title(str(n) + " x " + str(n) + " at " + str(temperature) + " K")
		subplotnumber += cols
		# print(subplotnumber)

		#magentisation subplot
		magax = figure.add_subplot(rows,cols,subplotnumber)
		magax.set_ylabel("Magnetisation per spin", fontsize=8)
		magax.set_xlabel("Monte Carlo Steps", fontsize=8)
		magax.plot(times, np.array(M)/spins)
		magax.tick_params(axis='both', which='major', labelsize=8)
		subplotnumber -= 2*cols
		# print(subplotnumber)
	subplotnumber += 2*cols
	# print(subplotnumber)


pl.show()


ILsavedata.py

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

# Defining the Ising lattice
  for n in [2,4,8,16,32]: #looping the predetermined sizes
      il = IsingLattice(n, n)
      il.lattice = np.ones((n, n))
  
      # Setting the number of Monte Carlo iterations
    runtime = 150000
     times = range(runtime)
 
     # Setting the temperature range
     temps = np.arange(1.2, 5.0000001, 0.2) #start, end, increment

    energies = []
      magnetisations = []
      energysq = []
      magnetisationsq = []
  
      for t in temps:
          for i in times:
              if i % 1000 == 0:
                  print(str(i)+"th Monte Carlo cycle at "+str(t)+"K on a "+str(n)+"x"+str(n)+" lattice")
            energy, magnetisation = il.montecarlostep(t)
        aveE, aveE2, aveM, aveM2, n_cycles = il.statistics()
        energies.append(aveE)
          energysq.append(aveE2)
  
          magnetisations.append(aveM)
          magnetisationsq.append(aveM2)
          #reset the IL object for the next cycle
        il.E = 0.0
          il.E2 = 0.0
          il.M = 0.0
          il.M2 = 0.0
          il.n_cycles = 0
  
      #Calculate errors: std error = square root of variance/number of cycles
    energyerrors = np.sqrt((np.array(energysq)-np.array(energies)**2)/n_cycles)
    magnetisationerrors = np.sqrt((np.array(magnetisationsq)-np.array(magnetisations)**2)/n_cycles)

    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))
    file_name = str(n) + "x" + str(n) + ".dat"
    np.savetxt(file_name, final_data)


ILplotsystemsize.py

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

enfig = pl.subplot(6,2,1)
enfig.set_ylabel("Energy \n per spin ($k_B$)")
enfig.set_xlabel("Temperature")
enfig.set_title("Comparing Energies in Different System Sizes")
magfig = pl.subplot(6,2,2)
magfig.set_ylabel("Magnetisation \n per spin")
magfig.set_xlabel("Temperature")
magfig.set_title("Comparing Magnetisations in Different System Sizes")
subplotnumber = 3

# Defining the Ising lattice
for n in [2,4,8,16,32]:
il = IsingLattice(n, n)
il.lattice = np.ones((n, n))
spins = n*n
string = str(n) + "x" + str(n)

array = np.loadtxt(string + ".dat") #load and store data
temps = array[:,0]
energies = array[:,1]
energysq = array[:,2]
magnetisations = array[:,3]
magnetisationssq = array[:,4]

#Plots
#energy
pl.subplot(6,2,subplotnumber) #y-axis on top figure
pl.title(string)
pl.ylabel("Energy per spin")
pl.xlabel("Temperature")
pl.plot(temps, np.array(energies)/spins)

energyerrors = np.sqrt((np.array(energysq)-np.array(energies)**2)/n_cycles) * 0.5
pl.errorbar(temps, np.array(energies)/spins, yerr=energyerrors, linestyle='o') #energy error bars
subplotnumber += 1

#plot on top graph for comparisons
enfig.plot(temps, np.array(energies)/spins, label = string)
enfig.errorbar(temps, np.array(energies)/spins, yerr=energyerrors, linestyle='o')

#magnetisation
pl.subplot(6,2,subplotnumber) #y-axis on bottom figure
pl.title(string)
pl.ylabel("Magnetisation \n per spin")
pl.xlabel("Temperature")
pl.plot(temps, np.array(magnetisations)/spins)

magnetisationerrors = np.sqrt((np.array(magnetisationsq)-np.array(magnetisations)**2)/n_cycles) * 0.5
pl.errorbar(temps, np.array(magnetisations)/spins, yerr=magnetisationerrors, linestyle='o') #mag error bars
subplotnumber += 1
#Calculate errors: std error = square root of (variance/number of cycles) where variance = <E2> - <E>**2

#plot on top graph for comparisons
magfig.plot(temps, np.array(magnetisations)/spins, label = string)
magfig.errorbar(temps, np.array(magnetisations)/spins, yerr=magnetisationerrors, linestyle='o')

final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))
file_name = str(n) + "x" + str(n) + ".dat"
np.savetxt(file_name, final_data)

magfig.legend(loc='upper right')
enfig.legend(loc='upper right')

pl.show()

ILcompare.py

from IsingLattice import *
from matplotlib import pylab as pl
from matplotlib import gridspec as gridspec
import numpy as np

#Define subplots
comparison_p = pl.subplot(4,2,1)
pl.title("A Comparison Of System Sizes (Python)")
pl.ylabel("Heat Capacity \n per spin ($k_B$)")
pl.xlabel("Temperature (K)")

comparison_c = pl.subplot(4,2,2)
pl.title("A Comparison Of System Sizes (C++)")
pl.ylabel("Heat Capacity \n per spin ($k_B$)")
pl.xlabel("Temperature (K)")

pl.rc('legend', **{'fontsize':9})

#Importing Data from C++ Files
subplotnumber = 3
for n in [2, 4, 8, 16, 32]:
spins = n**2

#Load Python data
file_name = str(n) + "x" + str(n) + ".dat"
array_p = np.loadtxt(file_name)
temps_p = array_p[:,0]
energies = array_p[:,1]
energysq = array_p[:,2]
#Calculate heat capacity per spins for array_p
heatcap_p = (energysq - energies ** 2)/(temps_p**2 * spins)

#Load C++ data
array_c = np.loadtxt("C++_data/" + file_name)
temps_c = array_c[:,0]
heatcap_c = array_c[:,5]
# array has these columns: T, E, E2, M, M2, C per spins

#Plot temp v. heat capacity
#on top comparison subplot
pl.subplot(4,2,1)
comparison_p.plot(temps_p, heatcap_p, label = file_name[:-4]) #add to comparison subplot
pl.subplot(4,2,2)
comparison_c.plot(temps_c, heatcap_c, label = file_name[:-4])

#new subplot showing this system size only
pl.subplot(4,2,subplotnumber)
subplotnumber +=1
pl.plot(temps_c, heatcap_c, label = "C++ Simulation Data")
pl.plot(temps_p, heatcap_p, label = "Python Simulation Data")
pl.title(file_name[:-4])
pl.grid(True)

#Construct plot axes and legend
pl.ylabel("Heat Capacity \n per spin ($k_B$)")
pl.xlabel("Temperature (K)")
pl.legend(loc="center left", bbox_to_anchor=(0.6, 0.5))

#Set a reasonable scale
pl.axis([min(temps_c), max(temps_c), 0, 2])

comparison_p.legend(loc="center left", bbox_to_anchor=(0.8, 0.5))
comparison_c.legend(loc="center left", bbox_to_anchor=(0.8, 0.5))

pl.show() #show the comparisons graph

ILplotheatcap.py

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

#Looping all the lattice sizes
 for n in [2,4,8,16,32]:
 	file_name = str(n) + "x" + str(n) + ".dat"
 	array = np.loadtxt(file_name) #load and store data
 	#array has these columns: temps, energies, energysq, magnetisations, magnetisationsq
 	spins = n**2
 
 	#Calculate heat capacity
 	pl.ylabel("Heat Capacity per spin")
 	pl.xlabel("Temperature (K)")
 	temps = array[:,0]
 	energies = array[:,1]
 	energysq = array[:,2]
 	heatcap = (energysq - energies**2)/(temps**2) #Var(E)/kT^2, where k is included in the units of heat cap
 
 	#plot heat capacity
 	pl.plot(temps, heatcap/spins, label=file_name[:-4])
 
 pl.legend(loc="center left", bbox_to_anchor=(1,0.5))
 
 pl.show()

ILtestfits.py

import IsingLattice
import numpy as np
from matplotlib import pylab as pl

#a function to plot a given polynomial
   def PlotPolynomial(polynomial, x, description): #input actual x data and label of the curve
   	lin_x = np.linspace(np.min(x), np.max(x), 1000) #generate 1000 evenly spaced points between min and max of temps data given
   	lin_y = np.polyval(polynomial, lin_x) #generate a smooth fitted line using the best fit
   	pl.plot(lin_x, lin_y, label = description) #plot the fit on graph
   
   #a function to return statistical data and maxima of a fit
def AnalysePolynomial(polynomial, x, y): #input actual x and y data
	y_hat = np.polyval(polynomial, x)
	y_bar = np.sum(y)/len(y) #mean of observed data
	ssres = np.sum((y-y_hat)**2) #sum of sqaures of residuals, i.e. value between model and observed
	sstot = np.sum((y-y_bar)**2)  #sum of total variance in the observed data
	ssreg = np.sum((y_hat-y_bar)**2)

	if sstot != 0:
		R2 = 1 - ssres/sstot
	else:
		R2 = 0 #ssres/sstot must not be infinity since the minimum value of R2 is zero

	Extrema = np.roots(np.polyder(fit)) #returns a list of all extrema x values
	#Determine a suitable extrema around the curie temperature
	Curie = 0.0
	for point in Extrema:
		if np.imag(point) == 0 and np.real(point) > 1 and np.real(point) < 5.: #if root is real and in the range 1 < Tc < 5
			Curie = np.real(point) #sometimes numpy returns a real root with a very small imaginary component because how floats are handled in python
	return R2, ssreg, ssres, sstot, Curie	

#define the data set in the test fit
   n=64
   file_name = str(n) + "x" + str(n) + ".dat"
 array = np.loadtxt("C++_data/" + file_name)
 temps = array[:,0]
   heatcap = array[:,5]
   
   #Plot the simulation data
   pl.plot(temps, heatcap, label = "C++ Simulation Data")
 
   #loop through various degrees of polynomials
for deg in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]:
	fit = np.polyfit(temps, heatcap, deg) #fit
	R2, ssreg, ssres, sstot, Curie = AnalysePolynomial(fit, temps, heatcap) #get coefficient of determiantion
	PlotPolynomial(fit, temps, "Test Fit, " + str(deg) + "th Polynomial \n R2:" + str(round(R2, 6))) #plot

#plot settings
   pl.rc('legend', **{'fontsize':10})
   pl.legend(loc="upper right")
   pl.ylabel("Heat Capacity per spin")
   pl.xlabel("Temperature")
   pl.show()

ILheatcapfit.py

from IsingLattice import *
from matplotlib import pylab as pl
import numpy as np

#a function to return stats and a positive extrema of a numpy polynomial
def AnalysePolynomial(polynomial, x, y): #input actual x and y data
	y_hat = np.polyval(polynomial, x) # y values from model
	y_bar = np.sum(y)/len(y) #mean of observed data
	ssres = np.sum((y-y_hat)**2) #sum of sqaures of residuals, i.e. value between model and observed
	sstot = np.sum((y-y_bar)**2)  #sum of total variance in the observed data
	ssreg = np.sum((y_hat-y_bar)**2)  #sum of squares of residuals in a regression model

	if sstot != 0:
		R2 = 1 - ssres/sstot
	else:
		R2 = 0 #if ssres/sstot approaches infinity, then R2 is at its minimum value: zero

	Extrema = np.roots(np.polyder(fit)) #returns a list of all extrema x values
	#Determine a suitable extrema around the curie temperature
	for point in Extrema:
		if np.imag(point) == 0 and np.real(point) > 1 and np.real(point) < 5.: #if root is real and in the range 1 < Tc < 5
			Curie = np.real(point) #sometimes numpy returns a real root with a very small imaginary component because how floats are handled in python
	return R2, ssreg, ssres, sstot, Curie

#a function to plot a given polynomial
def PlotPolynomial(polynomial, x, description): #input actual x data and label of the curve
	lin_x = np.linspace(np.min(x), np.max(x), 1000) #generate 1000 evenly spaced points between min and max of temps data given
	lin_y = np.polyval(polynomial, lin_x) #generate a smooth fitted line using the best fit
	pl.plot(lin_x, lin_y, label = description) #plot the fit on graph

#a function to return a text string from the polynomial (as given by np.polyfit())
def ToText(polynomial, limit=9999999999999, dp=1): #unlimited length of text string and 1dp by default
	equation = ""
	for j in range(len(polynomial)):
		i = len(polynomial) - 1 - j #i is the power of x because coefficients are given in decreasing powers of x
		coefficient = polynomial[j]
		if coefficient >= 0.005: #displays non-zero coefficients only
			equation += " + " + str(round(coefficient, dp)) #round coefficient 
			equation += r"x^{" + str(i) + r"}" #index of the coeffficient is the power to which T is raised
		elif coefficient <= 0.005: #displays non-zero coefficients only
			equation += str(round(coefficient, dp)) #round coefficient
			equation += r"x^{" + str(i) + r"}" 
		if len(equation) >= limit: #use this to avoid equation taking up too much space
			equation += " + ..."
			break
	return equation
	
#Define subplots
comparison = pl.subplot(4,2,(1,2))
pl.title("A Comparison Of System Sizes Without Polynomial Fit")
pl.ylabel("Heat Capacity per spin")
pl.xlabel("Temperature")

forwiki = ""
inversesizes = []
curies = []
#Importing Data from C++ Files
subplotnumber = 3
for n in [2, 4, 8, 16, 32]:
	file_name = str(n) + "x" + str(n) + ".dat"
	array = np.loadtxt("C++_data/" + file_name)
	# array has these columns: T, E, E2, M, M2, C per spins

	#Plot temp v. heat capacity
	#on top comparison subplot
	pl.subplot(4,2,(1,2))
	temps = array[:,0]
	heatcap = array[:,5]
	comparison.plot(temps, heatcap, label = file_name[:-4]) #add to comparison subplot

	#new subplot showing this system size only
	pl.subplot(4,2,subplotnumber) 
	subplotnumber +=1
	pl.plot(temps, heatcap, label = "C++ Simulation Data")
	pl.title(file_name[:-4])
	pl.grid(True)

	#First General Fit Over All Region
	fit = np.polyfit(temps, heatcap, 270) #fit the data up to the 270th power
	R2, ssreg, ssres, sstot, Curie = AnalysePolynomial(fit, temps, heatcap) #analyse the fits's error and maxima
	PlotPolynomial(fit, temps, "Fit Over All Region")

	#Generate text on graphs
	pl.text(0.55, 0.5, r"Over All Region: $" + ToText(fit, limit=80) + r"$") #add equations text
	pl.text(0.55, 0.35, "Coefficient of Determiantion: " + str(R2)) #add the coefficient of determiantion
	pl.text(0.55, 0.2, "Maxima: " + "(" + str(Curie) + ", " + str(np.polyval(fit, Curie)) + ")") #add extrema points

	#Now we do another fit on the particular range around the estimated Curie temperature
	#We use the coefficient of determination to get the range of particular data for the more precise fit
	Tmin = Curie * (1 - (1-R2))
	Tmax = Curie * (1 + (1-R2))
	selection = np.logical_and(temps>Tmin, temps<Tmax)
	select_temps, select_heatcap = temps[selection], heatcap[selection]

	#The particular temperature range should include at least 10 values around the Curie temperature for a reasonable fit no matter how accurate the original fit was
	if len(select_temps) < 10:
		index = np.argmin(abs(temps - Curie)) #index of the temperature with lowest difference with Curie temp
		select_temps = temps[index-5:index+4] #select 10 closest temps around Curie
		select_heatcap = heatcap[index-5:index+4] #select 10 closest heat capacities around peak

	fit = np.polyfit(select_temps, select_heatcap, 2) #fit the data in the particular range as defined above
	R2, ssreg, ssres, sstot, Curie = AnalysePolynomial(fit, select_temps, select_heatcap)
	PlotPolynomial(fit, temps, "Fit Around Peak") #Plot the new particular fit

	#Generate text shown on graphs
	pl.text(0.55, 1.8, r"Around Peak: $" + ToText(fit, limit=80) + r"$") #add equations text
	pl.text(0.55, 1.65, "Coefficient of Determiantion: " + str(R2)) #add the coefficient of determiantion
	pl.text(0.55, 1.5, "Maxima: " + "(" + str(Curie) + ", " + str(np.polyval(fit, Curie)) + ")") #add extrema points
	pl.legend(loc="upper right")

	#Display texts for wiki writing
	forwiki += file_name[:-4] + ": " + ToText(fit, dp=6) + "\n R2: " + str(R2) + "\n Maxima: " + "(" + str(Curie) + ", " + str(np.polyval(fit, Curie)) + ") \n\n"

	#Append data for later graph plotting on the finite system size effect
	inversesizes.append(1/n)
	curies.append(Curie)

	#Construct plot axes
	pl.ylabel("Heat Capacity per spin")
	pl.xlabel("Temperature /K")

	#Set a reasonable scale
	pl.axis([min(temps), max(temps), 0, 2]) #same scale for all sizes for comparison purposes

#Add legend to top comparisons graph (legend must be generated after all data has been plotted to prevent matplotlib error)
pl.subplot(4,2,(1,2))
pl.legend(loc="center left", bbox_to_anchor=(0.85,0.5))
pl.grid(True)
pl.rc('legend', **{'fontsize':9})

#Plot the linear graph for the finite system size effect
pl.subplot(4,2,8)
pl.scatter(inversesizes, curies)
pl.title("Finite Size Effect")
pl.ylabel("Curie Temperature from Simulation (K)")
pl.xlabel(r"Lattice Size$^{-1}$")
linearfit = np.polyfit(inversesizes, curies, 1) #get a linear fit on the graph
PlotPolynomial(linearfit, inversesizes, None) #plot the fit
R2, ssreg, ssres, sstot, Curie = AnalysePolynomial(linearfit, inversesizes, curies) #get coefficient of determiantion

pl.text(-0.075, 2.50, r"$" + ToText(linearfit, limit=80, dp=6) + r"$") #add equations text
pl.text(-0.075, 2.485, "Coefficient of Determiantion: " + str(R2)) #add the coefficient of determiantion
forwiki += "Finite Size Effect: " + ToText(linearfit, dp=6) + "\n R2: " + str(R2) #display texts for wiki writing

print(forwiki)
pl.show()

To produce the graph in the final Conclusion, I sliced the Finite System Size Effect data:

#Plot the linear graph for the finite system size effect
......
curies = curies[2:]
inversesizes = inversesizes[2:]
linearfit = np.polyfit(inversesizes, curies, 1) #get a linear fit on the graph
PlotPolynomial(linearfit, inversesizes, None) #plot the fit
R2, ssreg, ssres, sstot, Curie = AnalysePolynomial(linearfit, inversesizes, curies) #get coefficient of determiantion

pl.text(-0.075, 2.50, r"$" + ToText(linearfit, limit=80, dp=6) + r"$") #add equations text
pl.text(-0.075, 2.485, "Coefficient of Determiantion: " + str(R2)) #add the coefficient of determiantion
forwiki += "Finite Size Effect: " + ToText(linearfit, dp=6) + "\n R2: " + str(R2) #display texts for wiki writing

print(forwiki)
pl.show()