<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://chemwiki.ch.ic.ac.uk/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jgh116</id>
	<title>ChemWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://chemwiki.ch.ic.ac.uk/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jgh116"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Jgh116"/>
	<updated>2026-05-30T22:42:12Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Locating_the_Curie_temperature&amp;diff=814680</id>
		<title>Programming a 2D Ising Model/Locating the Curie temperature</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Locating_the_Curie_temperature&amp;diff=814680"/>
		<updated>2024-03-04T09:47:03Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the eighth (and final) section of the third year CMP experiment. You can return to the previous page, [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]], or go back to the [[Third year CMP compulsory experiment|Introduction]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You should have seen in the previous section that the heat capacity becomes strongly peaked in the vicinity of the critical temperature and that the peak became increasingly sharply peaked as the system size was increased &amp;amp;mdash; in fact, Onsager proved that in an infinite system the heat capacity should diverge at &amp;lt;math&amp;gt;T = T_C&amp;lt;/math&amp;gt;. In our finite systems, however, not only does the heat capacity not diverge, the Curie temperature changes with system size! This is known as a &#039;&#039;finite size effect&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
It can be shown, however, that the temperature at which the heat capacity has its maximum must scale according to &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;T_{C, L}&amp;lt;/math&amp;gt; is the Curie temperature of an &amp;lt;math&amp;gt;L\times L&amp;lt;/math&amp;gt;lattice, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant in which we are not especially interested.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 8a&amp;lt;/big&amp;gt;: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Each file contains six columns: &amp;lt;math&amp;gt;T, E, E^2, M, M^2, C&amp;lt;/math&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For &#039;&#039;one&#039;&#039; lattice size, save a PNG of this comparison and add it to your report &amp;amp;mdash; add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Polynomial fitting==&lt;br /&gt;
&lt;br /&gt;
To find the temperature at which the heat capacity and susceptibilities have their maxima, we are going to fit a polynomial to the data in the critical region. NumPy provides the useful [http://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html polyfit] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html#numpy.polyval polyval] functions for this purpose. The following code is written for the heat capacities - try to also repeat this for the susceptibility! The usage is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
data = np.loadtxt(&amp;quot;...&amp;quot;) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
T = data[:,0] #get the first column&lt;br /&gt;
C = data[:,1] # get the second column&lt;br /&gt;
&lt;br /&gt;
#first we fit the polynomial to the data&lt;br /&gt;
fit = np.polyfit(T, C, 3) # fit a third order polynomial&lt;br /&gt;
&lt;br /&gt;
#now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
T_min = np.min(T)&lt;br /&gt;
T_max = np.max(T)&lt;br /&gt;
T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 8b&amp;lt;/big&amp;gt;: write a script to read the data from a particular file, and plot C and &amp;lt;math&amp;gt;\chi&amp;lt;/math&amp;gt; vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit &amp;amp;mdash; in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===Fitting in a particular temperature range===&lt;br /&gt;
&lt;br /&gt;
Rather than fit to all of the data, we might want to fit in only a particular range. NumPy provides a very powerful way to index arrays based on certain conditions. For example, if we want to extract only those data points which are between a particular &amp;lt;math&amp;gt;T_{\mathrm{min}}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;T_{\mathrm{max}}&amp;lt;/math&amp;gt;, we can use the following syntax:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
data = np.loadtxt(&amp;quot;...&amp;quot;) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
T = data[:,0] #get the first column&lt;br /&gt;
C = data[:,1] # get the second column&lt;br /&gt;
&lt;br /&gt;
Tmin = 0.5 #for example&lt;br /&gt;
Tmax = 2.0 #for example&lt;br /&gt;
&lt;br /&gt;
selection = np.logical_and(T &amp;gt; Tmin, T &amp;lt; Tmax) #choose only those rows where both conditions are true&lt;br /&gt;
peak_T_values = T[selection]&lt;br /&gt;
peak_C_values = C[selection]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 8c&amp;lt;/big&amp;gt;: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak! You should find it easier to get a good fit when restricted to this region.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===Finding the peak in C===&lt;br /&gt;
&lt;br /&gt;
Your fitting script should now generate two variables: peak_T_range, containing 1000 equally spaced temperature values between &amp;lt;math&amp;gt;T_{\mathrm{min}}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;T_{\mathrm{max}}&amp;lt;/math&amp;gt; (whatever you chose those values to be), and fitted_C_values, containing the fitted heat capacity at each of those points. Use the NumPy max function to find the maximum in C. If you store the maximum value of C in the variable Cmax, you can use the following notation to find the corresponding temperature:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Cmax = np.max(...)&lt;br /&gt;
Tmax = peak_T_range[fitted_C_values == Cmax]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 8d&amp;lt;/big&amp;gt;: find the temperature at which the maximum in C occurs for each datafile that you were given or generated with Python. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the eighth (and final) section of the third year CMP experiment. You can return to the previous page, [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]], or go back to the [[Third year CMP compulsory experiment|Introduction]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Determining_the_heat_capacity&amp;diff=814679</id>
		<title>Programming a 2D Ising Model/Determining the heat capacity</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Determining_the_heat_capacity&amp;diff=814679"/>
		<updated>2024-03-04T09:43:21Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the seventh section of the third year CMP experiment. You can return to the previous page, [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]], or jump ahead to the next section, [[Third year CMP compulsory experiment/Locating the Curie temperature|Locating the Curie temperature]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Calculating the heat capacity==&lt;br /&gt;
&lt;br /&gt;
As we have seen, increasing the temperature above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; induces a phase transition &amp;amp;mdash; the magnetisation of the system rapidly drops, but it can be hard to use this information to pinpoint the Curie temperature itself. As well as demonstrating the closed form solution to the partition function that we mentioned in the introduction, Lars Onsager also demonstrated that the heat capacity of the 2D Ising model should become very strongly peaked at the phase transition temperature (in fact, when &amp;lt;math&amp;gt;T = T_C&amp;lt;/math&amp;gt; exactly, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; diverges).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 7a&amp;lt;/big&amp;gt;: By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&lt;br /&gt;
&lt;br /&gt;
Using this relation, and the data that you generated in the previous sections, you can now determine the heat capacity of your lattice as a function of temperature and system size. in a similar way, we can also define another quantity called the magnetic susceptibility, $\chi$, which depends on the average properties of the order parameter of the system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\chi = \beta(\left\langle M^2\right\rangle - \left\langle M\right\rangle^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which will behave in a similar way to the heat capacity. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 7b&amp;lt;/big&amp;gt;: Write a Python script to make a plot showing the heat capacity and susceptibility versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the seventh section of the third year CMP experiment. You can return to the previous page, [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]], or jump ahead to the next section, [[Third year CMP compulsory experiment/Locating the Curie temperature|Locating the Curie temperature]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813498</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813498"/>
		<updated>2021-01-12T14:16:46Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Assessment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment (Programming for simple simulations). This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In the first year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned in the first year to write a code to perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a report, writing up your findings. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or LaTeX, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The mark breakdown for the tasks is given below. You will also be assessed on the quality of the code and the report. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1a-c&#039;&#039;&#039;: 6 marks &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2a-b&#039;&#039;&#039;: 10 marks &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3a-c&#039;&#039;&#039;: 12 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4a-c&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5a-b&#039;&#039;&#039;: 12 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7a-b&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8a-d&#039;&#039;&#039;: 20 marks  &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Report + Code&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Total:&#039;&#039;&#039; 100 marks&lt;br /&gt;
&lt;br /&gt;
This mark will be divided by 5 and rounded to the nearest integer to obtain a final mark out of 20. &lt;br /&gt;
&lt;br /&gt;
For clarity, code included in the report should be written in a monospaced font (e.g. Courier) or a pasted screenshot. The former can be easily achieved in LaTeX by placing your code inside a &amp;lt;code&amp;gt;\texttt{}&amp;lt;/code&amp;gt; command. &#039;&#039;&#039;The code, except where relevant to discussion, can be placed in an appendix.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://www.anaconda.com/products/individual Anaconda] scientific Python distribution (if you are a Windows, Mac or Linux user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hedley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Introduction_to_the_Ising_model&amp;diff=813390</id>
		<title>Programming a 2D Ising Model/Introduction to the Ising model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model/Introduction_to_the_Ising_model&amp;diff=813390"/>
		<updated>2020-11-23T11:15:15Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the first section of the third year CMP experiment. You can return to the introduction page, [[Third year CMP compulsory experiment]], or jump ahead to the next section, [[Third year CMP compulsory experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==The model==&lt;br /&gt;
The Ising model is a simple physical model proposed in the 1920s to understand the behaviour of ferromagnets. In most materials, the magnetic dipoles of the atoms have no preferred orientation, and the bulk material has no net magnetic moment. In certain materials, however, there is preferential alignment of the magnetic dipoles along certain directions. These materials, such as iron, exhibit an overall magnetic moment, and are called ferromagnetic.&lt;br /&gt;
&lt;br /&gt;
The phenomenon is based on competition between two physical effects &amp;amp;mdash; energy minimisation and entropy maximisation. On the one hand, parallel alignment of magnetic moments is energetically favourable. On the other hand, aligning the system in this way reduces the entropy.&lt;br /&gt;
&lt;br /&gt;
In the Ising model, we consider a collection of spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, each of which can either have the value &amp;quot;up&amp;quot; (&amp;lt;math&amp;gt;+1&amp;lt;/math&amp;gt;) or &amp;quot;down&amp;quot; (&amp;lt;math&amp;gt;-1&amp;lt;/math&amp;gt;), arranged on a lattice. In one dimension, this takes the form of a &amp;quot;chain&amp;quot; of adjacent spins, &amp;lt;math&amp;gt;s_1&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;s_N&amp;lt;/math&amp;gt;. In two dimensions we have a grid of cells, each of which contains one spin. In three dimensions, the system is a cuboid, and so on. The arrangements for 1, 2 and 3 dimensions are illustrated in &#039;&#039;&#039;figure 1&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The rules of the model are simple:&lt;br /&gt;
&lt;br /&gt;
* When no magnetic field is applied, the only energetic contribution comes from the interaction between spins in &#039;&#039;adjacent&#039;&#039; lattice cells&lt;br /&gt;
* The total interaction energy is defined as &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt; is a constant which controls the strength of interaction and the notation &amp;lt;math&amp;gt;j\  \in\  \mathrm{neighbours}\left(i\right)&amp;lt;/math&amp;gt; indicates that spin &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; must lie in a lattice cell adjacent to spin &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;. The factor of one half is included to make sure that we do not count each spin-spin interaction twice.&lt;br /&gt;
* Periodic boundary conditions are applied. In other words, the &amp;quot;final&amp;quot; spin in a particular direction is considered to be adjacent to the &amp;quot;first&amp;quot; spin in that direction. In &#039;&#039;&#039;figure 2&#039;&#039;&#039;, for example, cell 1 has neighbour interactions with cells 2, 3, 4, and 7, while cell 9 interacts with cells 3, 6, 7, and 8.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingNumberedCell.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration of a 2D Ising lattice, with numbered cells to indicate the neighbour interactions.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 1a&amp;lt;/big&amp;gt;: Show that the lowest possible energy for the Ising model (with &amp;lt;math&amp;gt;J&amp;gt;0&amp;lt;/math&amp;gt;) is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
The Ising model is interesting because it is one of the simplest physical models to display a phase transition (so long as we are in two dimensions or more). The temperature of the system controls the balance between the energetic driving force, which wants the system to adopt the lowest energy configuration that you imagined in the previous section, and the entropic driving force, which seeks to maximise the number of configurations available to the system.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 1b&amp;lt;/big&amp;gt;: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At low temperatures, little thermal energy is available to overcome such energy barriers, and we expect the system to adopt the lowest energy (the energetic driving force will dominate). Under these conditions, we expect a large number of parallel spins, and we can characterise this by considering the total &#039;&#039;magnetisation&#039;&#039; of the system: &amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;. At higher temperatures, however, enough thermal energy will be available that spins can flip relatively freely. Under these conditions, the entropic driving force should dominate. The transition temperature between the two regimes is called the &#039;&#039;Curie temperature&#039;&#039;, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;. Below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, we observe the highly ordered magnetised phase, while above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; a disordered, demagnetised, phase emerges.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;TASK 1c&amp;lt;/big&amp;gt;: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
We are interested, therefore, in determining the magnetisation &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, and energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;, of the system as functions of temperature &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;. We can write statistical mechanical equations for these quantities:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle_T = \frac{1}{Z}\sum_\alpha M\left(\alpha\right) \exp \left\{-\frac{E\left(\alpha\right)}{k_BT}\right\} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle E\right\rangle_T = \frac{1}{Z}\sum_\alpha E\left(\alpha\right) \exp \left\{-\frac{E\left(\alpha\right)}{k_BT}\right\} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; is used as a shorthand to represent all spins in the system, and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function. &#039;&#039;&#039;If you are doing this experiment before the statistical thermodynamics lecture course, you may not have seen expressions of this nature before. If so, please ask or e-mail the demonstrator, who will be happy to give you a brief introduction to the topic.&#039;&#039;&#039; It was discovered relatively quickly after the model was first proposed that the partition function in the one dimensional case can be solved analytically (though doing so is beyond the scope of the experiment):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Z\left(T, N\right) = \left[2\cosh\left(\frac{J}{k_BT}\right)\right]^N&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Much later, [http://en.wikipedia.org/wiki/Lars_Onsager Lars Onsager] established that the two dimensional partition function can also be found analytically, though the mathematics is significantly more involved and the resulting expression is correspondingly much more complicated. In dimensions greater than two, no analytical solution is known.&lt;br /&gt;
&lt;br /&gt;
Luckily for us, the problem can instead be tackled by numerical methods, and it is this that will be the focus of the experiment. We will discuss how the equations for &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; can be solved, but first we must set up a Python script to model the lattice.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:blue; &amp;quot;&amp;gt;This is the first section of the third year CMP experiment. You can return to the introduction page, [[Third year CMP compulsory experiment]], or jump ahead to the next section, [[Third year CMP compulsory experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]].&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813308</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813308"/>
		<updated>2020-10-26T14:10:38Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Assessment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment (Programming for simple simulations). This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In the first year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned in the first year to write a code to perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a report, writing up your findings. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or LaTeX, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The mark breakdown for the tasks is given below. You will also be assessed on the quality of the code and the report. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1a-c&#039;&#039;&#039;: 6 marks &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2a-b&#039;&#039;&#039;: 10 marks &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3a-c&#039;&#039;&#039;: 12 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4a-c&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5a-b&#039;&#039;&#039;: 12 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7a-b&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8a-d&#039;&#039;&#039;: 20 marks  &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Report + Code&#039;&#039;&#039;: 10 marks&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Total:&#039;&#039;&#039; 100 marks&lt;br /&gt;
&lt;br /&gt;
This mark will be divided by 5 and rounded to the nearest half integer to obtain a final mark out of 20. &lt;br /&gt;
&lt;br /&gt;
For clarity, code included in the report should be written in a monospaced font (e.g. Courier) or a pasted screenshot. The former can be easily achieved in LaTeX by placing your code inside a &amp;lt;code&amp;gt;\texttt{}&amp;lt;/code&amp;gt; command. &#039;&#039;&#039;The code, except where relevant to discussion, can be placed in an appendix.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://www.anaconda.com/products/individual Anaconda] scientific Python distribution (if you are a Windows, Mac or Linux user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hedley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813307</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813307"/>
		<updated>2020-10-26T14:07:59Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment (Programming for simple simulations). This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In the first year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned in the first year to write a code to perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a report, writing up your findings. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or LaTeX, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The mark breakdown for the tasks is given below. You will also be assessed on the quality of the code and the report. &lt;br /&gt;
&lt;br /&gt;
Task 1a-c: 6 marks&lt;br /&gt;
Task 2a-b: 10 marks &lt;br /&gt;
Task 3a-c: 12 marks&lt;br /&gt;
Task 4a-c: 10 marks&lt;br /&gt;
Task 5a-b: 12 marks&lt;br /&gt;
Task 6: 10 marks&lt;br /&gt;
Task 7a-b: 10 marks&lt;br /&gt;
Task 8a-d: 20 marks  &lt;br /&gt;
Report + Code: 10 marks&lt;br /&gt;
&lt;br /&gt;
Total: 100 marks&lt;br /&gt;
&lt;br /&gt;
This mark will be divided by 5 and rounded to the nearest half integer to obtain a final mark out of 20. &lt;br /&gt;
&lt;br /&gt;
For clarity, code included in the report should be written in a monospaced font (e.g. Courier) or a pasted screenshot. The former can be easily achieved in LaTeX by placing your code inside a &amp;lt;code&amp;gt;\texttt{}&amp;lt;/code&amp;gt; command. &#039;&#039;&#039;The code, except where relevant to discussion, can be placed in an appendix.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://www.anaconda.com/products/individual Anaconda] scientific Python distribution (if you are a Windows, Mac or Linux user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hedley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813249</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813249"/>
		<updated>2020-10-15T10:02:37Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment. This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
Last year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned last year to write a code perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a report, writing up your findings. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or Latex, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://store.continuum.io/cshop/anaconda/ Anaconda] scientific Python distribution (if you are a Windows or Mac user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hedley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813248</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813248"/>
		<updated>2020-10-15T10:00:53Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment. This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
Last year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned last year to write a code perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a &amp;quot;report&amp;quot; in wiki form. You can find instructions on how to create the wiki page and edit it in the &amp;quot;Report Preparation&amp;quot; section of [[Mod:writeup#Report_Preparation|this page]]. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or Latex, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://store.continuum.io/cshop/anaconda/ Anaconda] scientific Python distribution (if you are a Windows or Mac user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hedley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813247</id>
		<title>Programming a 2D Ising Model</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Programming_a_2D_Ising_Model&amp;diff=813247"/>
		<updated>2020-10-15T10:00:22Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;&amp;lt;big&amp;gt;This is the Programming-Ising experiment. This experiment is compulsory for students taking the chemistry with molecular physics option. If you are looking for the 3rd year Liquid simulation experiment, you will find it [[Third_year_simulation_experiment|here]].&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
Last year, you were introduced to [https://www.python.org/ Python], a scripting language which is rapidly becoming the de facto language for everyday scientific programming. Python is an interpreted, rather than compiled language, and is rather more forgiving than older languages such as C or FORTRAN. This reduces the amount of time that we need to spend programming and debugging. The downside to all this is that the execution of a Python program is much slower than a compiled equivalent. As a compromise, we usually let large codes written in a compiled language (usually C/C++) do the hefty numerical work for us, and then use scripting languages like Python to analyse the results.&lt;br /&gt;
&lt;br /&gt;
These large codes for numerical work (you may have already used GAUSSIAN for electronic structure calculations) typically take arcane text files as input, and produce equally arcane text files as output. If, for the sake of example, you run twenty different molecular dynamics simulations, and each of them produces an output file which contains information about the density of the system, then extracting this information by hand would be very tedious (and if you run hundreds or thousands of simulations, virtually impossible), but this sort of task is the thing at which languages like Python really excel.&lt;br /&gt;
&lt;br /&gt;
In this exercise, you are going to use the Python that you learned last year to write a code perform Monte-Carlo simulations of the 2D [http://en.wikipedia.org/wiki/Ising_model Ising model], a set of spins on a lattice which is used to model ferromagnetic behaviour, and also to analyse the results of the simulation to find the heat capacity of the system and the Curie temperature &amp;amp;mdash; the temperature below which the system is able to maintain a spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Assessment==&lt;br /&gt;
&lt;br /&gt;
At the end of this experiment you must submit a &amp;quot;report&amp;quot; in wiki form. You can find instructions on how to create the wiki page and edit it in the &amp;quot;Report Preparation&amp;quot; section of [[Mod:writeup#Report_Preparation|this page]]. Each section of the experiment has a number of tasks that you should complete, labelled &#039;&#039;&#039;&amp;lt;big&amp;gt;TASK&amp;lt;/big&amp;gt;&#039;&#039;&#039;. If this is a mathematical exercise, your report should contain a short summary of the solution. If it is a graphical exercise, you should include the relevant image. Your report should explain briefly what you did in each stage of the experiment, and what your findings were.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT BY 12 NOON ON THE WEDNESDAY FOLLOWING THE EXPERIMENT.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;YOU MUST SUBMIT YOUR REPORT USING BLACKBOARD - INSTRUCTIONS WILL BE PROVIDED BY EMAIL BEFORE THE DEADLINE.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&amp;lt;big&amp;gt;&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;Your report should contain the source code to *any* Python scripts that you write during the experiment, annotated with comments. You can write your report in Word or Latex, but you should submit a **single PDF file**.&amp;lt;/span&amp;gt;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If you wish, you are welcome to do the experiment on your own computer. You will need the [https://store.continuum.io/cshop/anaconda/ Anaconda] scientific Python distribution (if you are a Windows or Mac user). You may ask for help with installing this, but it is not part of the experiment &amp;amp;mdash; students who have scientific questions will take priority.&lt;br /&gt;
&lt;br /&gt;
==Getting Help==&lt;br /&gt;
&lt;br /&gt;
The demonstrators for this exercise are Aidan Chapman (aidan.chapman16@imperial.ac.uk), Jonathan Hedley (jonathan.hedley16@imperial.ac.uk), Dr. Juan Olarte-Plata (j.olarte@imperial.ac.uk) and Dr Suman Saurab (s.saurabh@imperial.ac.uk). The assessors will be Aidan Chapman, Jonathan Hetley and Prof. Fernando Bresme. The demonstrators will be available online via Teams between 10-12 and 2-4 pm on each day of the experiment (&#039;&#039;&#039;Note: the experiment does not &amp;quot;run&amp;quot; on Wednesdays&#039;&#039;&#039;). If you have questions outside of these times, you are of course welcome to send them by e-mail.&lt;br /&gt;
&lt;br /&gt;
The member of academic staff responsible for this exercise is Prof. Fernando Bresme (f.bresme@imperial.ac.uk).&lt;br /&gt;
&lt;br /&gt;
==Structure of this Experiment==&lt;br /&gt;
&lt;br /&gt;
This experimental manual has been broken up into a number of subsections. Direct links to each of them may be found below. You should attempt them in order, and you should complete all of them to finish the experiment.&lt;br /&gt;
&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model|Introduction to the Ising model]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Calculating the energy and magnetisation|Calculating the energy and magnetisation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Introduction to Monte Carlo simulation|Introduction to the Monte Carlo simulation]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Accelerating the code|Accelerating the code]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of temperature|The effect of temperature]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/The effect of system size|The effect of system size]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Determining the heat capacity|Determining the heat capacity]]&lt;br /&gt;
# [[Third_year_CMP_compulsory_experiment/Locating the Curie temperature|Locating the Curie temperature]]&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737110</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737110"/>
		<updated>2018-11-21T11:56:22Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Modifying IsingLattice.py */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows&amp;lt;ref&amp;gt;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_Monte_Carlo_simulation&amp;lt;/ref&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition&amp;lt;ref&amp;gt;Fernando Bresme, Imperial College London&amp;lt;/ref&amp;gt;. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51798798798799||2.437327327327329||2.3566666666666687||2.3281981981982||2.285495495495497&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 21&#039;&#039;&#039; A plot of Lattice Size vs. Curie Temperature, showing the inverse nature of the relationship&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Invcurietemp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 22&#039;&#039;&#039; A plot of &amp;lt;math&amp;gt;\frac{1}{L}&amp;lt;/math&amp;gt; vs Curie Temperature&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.292810310310312 \pm 1.09% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.04%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737107</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737107"/>
		<updated>2018-11-21T11:54:37Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition&amp;lt;ref&amp;gt;Fernando Bresme, Imperial College London&amp;lt;/ref&amp;gt;. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51798798798799||2.437327327327329||2.3566666666666687||2.3281981981982||2.285495495495497&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 21&#039;&#039;&#039; A plot of Lattice Size vs. Curie Temperature, showing the inverse nature of the relationship&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Invcurietemp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 22&#039;&#039;&#039; A plot of &amp;lt;math&amp;gt;\frac{1}{L}&amp;lt;/math&amp;gt; vs Curie Temperature&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.292810310310312 \pm 1.09% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.04%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737098</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737098"/>
		<updated>2018-11-21T11:49:07Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51798798798799||2.437327327327329||2.3566666666666687||2.3281981981982||2.285495495495497&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 21&#039;&#039;&#039; A plot of Lattice Size vs. Curie Temperature, showing the inverse nature of the relationship&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Invcurietemp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 22&#039;&#039;&#039; A plot of &amp;lt;math&amp;gt;\frac{1}{L}&amp;lt;/math&amp;gt; vs Curie Temperature&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.292810310310312 \pm 1.09% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.04%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737094</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737094"/>
		<updated>2018-11-21T11:47:25Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 21&#039;&#039;&#039; A plot of Lattice Size vs. Curie Temperature, showing the inverse nature of the relationship&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Invcurietemp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 22&#039;&#039;&#039; A plot of &amp;lt;math&amp;gt;\frac{1}{L}&amp;lt;/math&amp;gt; vs Curie Temperature&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.292810310310312 \pm 1.09% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.04%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737054</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737054"/>
		<updated>2018-11-21T11:07:30Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 21&#039;&#039;&#039; A plot of Lattice Size vs. Curie Temperature, showing the inverse nature of the relationship&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Invcurietemp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 22&#039;&#039;&#039; A plot of &amp;lt;math&amp;gt;\frac{1}{L}&amp;lt;/math&amp;gt; vs Curie Temperature&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737049</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737049"/>
		<updated>2018-11-21T11:05:53Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Calculating the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039; Plot of the Heat Capacity vs temperature for varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737047</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737047"/>
		<updated>2018-11-21T11:04:57Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039; A gif showing how the energy varies with temperature with varying lattice sizes]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039; A gif showing how the magnetisation varies with temperature with varying lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737044</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737044"/>
		<updated>2018-11-21T11:03:59Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039; Graphs showing how the energy and magnetisation vary with temperature for an 8x8 lattice]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737042</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737042"/>
		<updated>2018-11-21T11:02:56Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737041</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737041"/>
		<updated>2018-11-21T11:01:58Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.51324324||2.43732733||2.35666667||2.32819822||2.2854955&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Invcurietemp.png&amp;diff=737038</id>
		<title>File:JGH116Invcurietemp.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Invcurietemp.png&amp;diff=737038"/>
		<updated>2018-11-21T11:00:52Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: Jgh116 uploaded a new version of File:JGH116Invcurietemp.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Curieplot.png&amp;diff=737037</id>
		<title>File:JGH116Curieplot.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Curieplot.png&amp;diff=737037"/>
		<updated>2018-11-21T11:00:22Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: Jgh116 uploaded a new version of File:JGH116Curieplot.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737036</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737036"/>
		<updated>2018-11-21T10:59:48Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.2937988 \pm 0.011% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~1.07%. &lt;br /&gt;
&lt;br /&gt;
The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737018</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737018"/>
		<updated>2018-11-21T10:48:19Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 &amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737014</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737014"/>
		<updated>2018-11-21T10:45:48Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737013</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737013"/>
		<updated>2018-11-21T10:45:04Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; had to be modified to account for the new variables defined in the constructor. After each temperature simulation, all the variables had to be reset:&lt;br /&gt;
 &lt;br /&gt;
 il.E = 0.0&lt;br /&gt;
 il.E2 = 0.0&lt;br /&gt;
 il.M = 0.0&lt;br /&gt;
 il.M2 = 0.0&lt;br /&gt;
 il.n_cycles = 0&lt;br /&gt;
 il.check=False&lt;br /&gt;
 il.n_threshold=0&lt;br /&gt;
 il.elist=[]&lt;br /&gt;
 il.mlist=[]&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737009</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737009"/>
		<updated>2018-11-21T10:35:50Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It was found that the averaging code written was having issues with recording data at high temperature, as the fluctuations became so large that the equilibrium conditions set were never met. Therefore, the &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function was modified to include a case for which the system had not been in equilibrium:&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):&lt;br /&gt;
     &amp;quot;Calculates the correct values for the averages of E,E*E (E2), M, M*M (M2), and returns them&amp;quot;&lt;br /&gt;
     if self.check==False: #this checks to see if the system is in equilibrium - if not, then it takes the last 20000 steps&lt;br /&gt;
         Earray=np.array(self.elist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         E=np.sum(Earray)/20000&lt;br /&gt;
         E2=np.sum(np.multiply(Earray,Earray))/20000&lt;br /&gt;
         Marray=np.array(self.mlist[self.n_cycles-20000:self.n_cycles])&lt;br /&gt;
         M=np.sum(Marray)/20000&lt;br /&gt;
         M2=np.sum(np.multiply(Marray,Marray))/20000&lt;br /&gt;
         return E,E2,M,M2,20000&lt;br /&gt;
     else: &lt;br /&gt;
         E=self.E/self.n_threshold&lt;br /&gt;
         E2=self.E2/self.n_threshold&lt;br /&gt;
         M=self.M/self.n_threshold&lt;br /&gt;
         M2=self.M2/self.n_threshold&lt;br /&gt;
         return E,E2,M,M2,self.n_threshold&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737006</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737006"/>
		<updated>2018-11-21T10:27:11Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(&#039;Equilibrium at:&#039;, self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 0.01 for 10000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;8x8 Lattice at a temperature of 5 for 20000 Monte Carlo Steps.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737005</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=737005"/>
		<updated>2018-11-21T10:23:47Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
File:JGH116CorrAv1.png|&#039;&#039;&#039;Figure&#039;&#039;&#039;&lt;br /&gt;
File:CorrAv2.png|&#039;&#039;&#039;Figure&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:CorrAv2.png&amp;diff=737004</id>
		<title>File:CorrAv2.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:CorrAv2.png&amp;diff=737004"/>
		<updated>2018-11-21T10:23:28Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116CorrAv1.png&amp;diff=737003</id>
		<title>File:JGH116CorrAv1.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116CorrAv1.png&amp;diff=737003"/>
		<updated>2018-11-21T10:22:43Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736999</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736999"/>
		<updated>2018-11-21T10:14:03Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases.&amp;lt;ref name=&amp;quot;gold&amp;quot;&amp;gt;[http://goldbook.iupac.org/index.html IUPAC Gold book], [http://goldbook.iupac.org/S05869.html Spinodal decomposition entry].&amp;lt;/ref&amp;gt; Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736997</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736997"/>
		<updated>2018-11-21T10:12:49Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Average Energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D&amp;lt;ref&amp;gt;[https://journals.aps.org/pr/abstract/10.1103/PhysRev.65.117 Lars Onsager, &#039;&#039;Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition&#039;&#039;]&amp;lt;/ref&amp;gt; Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736984</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736984"/>
		<updated>2018-11-21T10:06:03Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D&amp;lt;ref&amp;gt;[http://www.hs-augsburg.de/~harsch/anglica/Chronology/20thC/Ising/isi_fm00.html Ernst Ising, &#039;&#039;Contribution to the Theory of Ferromagnetism&#039;&#039;]&amp;lt;/ref&amp;gt; and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736981</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736981"/>
		<updated>2018-11-21T10:01:40Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Calculating the Energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using &amp;lt;code&amp;gt;[i+1]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;[j+1]&amp;lt;/code&amp;gt; would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for &amp;lt;code&amp;gt;[i-1]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;[j-1]&amp;lt;/code&amp;gt;, as the index of &amp;lt;code&amp;gt;[-1]&amp;lt;/code&amp;gt; returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736979</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736979"/>
		<updated>2018-11-21T09:59:52Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for the phenomena of permanent magnets.&amp;lt;ref name=Chikazumi&amp;gt;{{cite book|last=Chikazumi|first=Sōshin|title=Physics of ferromagnetism|year=2009|publisher=Oxford University Press|location=Oxford|isbn=9780199564811|edition=2nd |others= English edition prepared with the assistance of C.D. Graham, Jr |page=118}}&amp;lt;/ref&amp;gt; A material can be described as ferromagnetic is if exhibits &#039;spontaneous magnetisation&#039;, i.e. it has a net magnetic moment in the absence of an external field. This occurs if the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. &lt;br /&gt;
&lt;br /&gt;
The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736968</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736968"/>
		<updated>2018-11-21T09:53:32Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736962</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736962"/>
		<updated>2018-11-21T09:52:36Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Closing Remarks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736959</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736959"/>
		<updated>2018-11-21T09:52:17Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics.&amp;lt;ref&amp;gt;https://en.wikipedia.org/wiki/Ising_model&amp;lt;/ref&amp;gt; Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736954</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736954"/>
		<updated>2018-11-21T09:48:20Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Closing Remarks=&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point. By applying some simple code to an array, it was possible to accurately model a complicated physical system, without the need for heavy computation of partition functions.&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736949</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736949"/>
		<updated>2018-11-21T09:43:30Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
The beauty of the Ising Model is its simplicity. By applying two simple rules to a lattice of spins (equations [1] and [7]), it is possible to observe a phase transition at the Curie Temperature. The model even shows effects of criticality close to the critical point.&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736938</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736938"/>
		<updated>2018-11-21T09:37:57Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the temperature at which the magnetisation drops increases as the lattice size increases. This can be attributed to long range fluctuations. It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736935</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736935"/>
		<updated>2018-11-21T09:34:42Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises. This is due to randomly flipped spins being more likely to be accepted at a higher temperature, varying the energy even more. The opposite occurs in the magnetisation. As temperature increases, the error bars shrink. This is because the ratio of up and down spins tend to 1:1 at temperature increases. Therefore the magnetisation gets closer to zero, and the error in this reduces consequentially.&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736931</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736931"/>
		<updated>2018-11-21T09:27:00Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Calculating the Energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. For a spin at the edge of the lattice, indexing using [i+1] or [j+1] would not work, as the index exceeds the size of the array. Therefore, the remainder of [i+1] and [j+1] with respect to the lattice size was taken in order to return the index back to zero for the edge. This was not a problem for [i-1] and [j-1], as the index of [-1] returns the desired element of the array. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)): #for each row&lt;br /&gt;
         for j in range(0,len(self.lattice[i])): #for each element&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols] #taking the remainder&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j] #taking the remainder&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4 &lt;br /&gt;
     return -0.5*energy #divide by 2 to account for double counting of interactions&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736921</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736921"/>
		<updated>2018-11-21T09:19:17Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based on an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736920</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736920"/>
		<updated>2018-11-21T09:18:34Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based off an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form a d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. It is important to note that spins on the edge of the lattice &#039;wrap around&#039; to interact with the spin on the opposite side of the lattice, making the lattice periodic in space. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736918</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736918"/>
		<updated>2018-11-21T09:14:47Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based off an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form at d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039; A simulation of a 100x100 lattice at a temperature of 0.01. The &#039;phase&#039; separation visible here is analogous to Spinodal Decomposition.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736915</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736915"/>
		<updated>2018-11-21T09:12:48Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based off an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form at d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation. This shows an example of spinodal decomposition. This is normally applied to the unmixing of a mixture of liquids or solids in one thermodynamic phase to form two coexisting phases. Here, the two different spins (spin up or spin down) can be considered as different phases. At the beginning of the simulation, the distribution of spins is random, much like a mixture of two phases. As Monte Carlo steps are applied (analogous to cooling the system), these spins &#039;unmix&#039; in order to reduce the free energy, i.e. clusters of the same spin start to form as there is no energy barrier to the nucleation of the &#039;spin up&#039;-rich and &#039;spin down&#039;-rich phases. As the lattices are periodic, they can be tiled, which emphasises these clusters of different phases.         &lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736828</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736828"/>
		<updated>2018-11-21T03:32:56Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Introduction to the Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism is the strongest type of magnetism that exists and is responsible for permanent magnets. This occurs when the magnetic domains (regions in which the spins of large numbers of unpaired electrons are parallel) in a material align. The Ising Model is incredibly versatile, and can be used to describe Ionic Liquids, Lattice Gases, and can even be applied in neuroscience. Here, we use the Ising Model as a pedagogical tool to understand the Metropolis Monte Carlo algorithm.  &lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
The Ising Model is based off an &#039;Ising&#039; Lattice. Consider a set of lattice sites, each with their own neighbours which form at d-dimensional lattice. At each site, there is a discrete variable, s, which represents the &#039;spin&#039; of the sites, where s ∈ {+1, -1}.&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation.   &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
WRITE SOMETHING ABOUT SPINODAL DECOMPOSITION&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736802</id>
		<title>Rep:JGH116-CMP-Prog</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:JGH116-CMP-Prog&amp;diff=736802"/>
		<updated>2018-11-21T03:14:02Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Introduction to the Ising Model =&lt;br /&gt;
&lt;br /&gt;
The Ising Model was introduced by Wilhelm Lenz in 1920 as a problem to his student, Ernst Ising, to model ferromagnetism in statistical mechanics. Ferromagnetism &lt;br /&gt;
&lt;br /&gt;
WRITE SOME INTRO STUFF&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin.]]&lt;br /&gt;
&lt;br /&gt;
For any two adjacent spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, there is an interaction energy J&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;ij&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; ∀ &amp;lt;i&amp;gt;i,j&amp;lt;/i&amp;gt;. The total internal energy for a given configuration of spins, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = -\sum_{\langle i~j\rangle} J_{ij} s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[1]&#039;&#039;&#039;                                                     &lt;br /&gt;
&lt;br /&gt;
where ⟨&#039;&#039;i j&#039;&#039;⟩ denotes a distinct pair of adjacent spins, with spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;. Assuming that all pairs of spins s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, have the same interaction energy, then it is possible to set &amp;lt;i&amp;gt;J&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = J&amp;lt;/i&amp;gt; ∀ ⟨&#039;&#039;i j&#039;&#039;⟩ ∈ ⍺. The total internal energy can therefore be rewritten as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[2]&#039;&#039;&#039;                    &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;adj(i)&amp;lt;/i&amp;gt; denotes every spin &amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt; adjacent to spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;. The factor of ½ is included to account for the double counting of interactions in the sum. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1D lattice, the number of neighbours, N&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt;, for a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2. For a 2D lattice, the number of neighbours is 4, and for a 3D lattice, the number of neighbours is 6. Therefore, the number of neighbours for any given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; is 2D, where D is the number of dimensions. &lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration is when all spins are parallel, i.e. s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = ±1 ∀ &amp;lt;i&amp;gt;i, j&amp;lt;/i&amp;gt;. Therefore, the product of any two spins in this configuration is always equal to 1 (s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; * s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;j&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; = 1):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;E_{\alpha} = - \frac{1}{2} J \sum_i^N\sum_{j\  \in\  \mathrm{adj}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J \sum_i^N 2D*1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - \frac{1}{2} J*N*2D &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = - DNJ &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[3]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The entropy for a given state is given by Boltzmann&#039;s equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[4]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where k&amp;lt;sub&amp;gt;b&amp;lt;/sub&amp;gt; is Boltzmann&#039;s constant (1.381 x 10&amp;lt;sup&amp;gt;23&amp;lt;/sup&amp;gt; J K&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;), and Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; is the multiplicity of the state.&lt;br /&gt;
&lt;br /&gt;
For a state containing N spins, the multiplicity of the state, Ω, is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega = \frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[5]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑,↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; is the number of spin up and spin down sites respectively, such that &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; + &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt; = N. For degenerate states, the multiplicity must be adapted to account for this. This can be done by multiplying the multiplicity by the degeneracy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[6]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Ω&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt;, g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; are the multiplicity and the degeneracy of a given configuration ⍺.&lt;br /&gt;
&lt;br /&gt;
For a state where all the spins are parallel, it is doubly degenerate (g&amp;lt;sub&amp;gt;⍺&amp;lt;/sub&amp;gt; = 2), as all spins can either be up (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↑&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;) or all spins are down (N = &amp;lt;i&amp;gt;n&amp;lt;sub&amp;gt;↓&amp;lt;/sub&amp;gt;&amp;lt;/i&amp;gt;). Therefore the multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Omega_\alpha = g_\alpha\frac{N!}{n_{\uparrow}!n_{\downarrow}!} &amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 \frac{N!}{n_{\uparrow}!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; = 2 * 1 = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of the lowest energy state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_\alpha = k_b \ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Phase Transitions==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)? How much entropy does the system gain by doing so?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 Energy Diagram.png|300px|thumb|right|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: Illustration showing the energy change when one spin is flipped in the lowest energy configuration of an Ising Lattice.]]&lt;br /&gt;
&lt;br /&gt;
As shown in figure 2, the number of interactions one spin has is in 3D is six - i.e. it interacts with each of its neighbours. When all the spins are parallel as in the lowest energy configuration, the relative total interaction energy  is -6J. When that spin is removed, that interaction energy is lost, taking the total energy up to 0J. If that spin is replaced, pointing down instead of up, then there are only unfavourable interactions, bringing the total energy up to +6J. Therefore, the overall energy change by flipping one spin in the lowest energy configuration in 3D is 12J. &lt;br /&gt;
&lt;br /&gt;
For a 3D system with 1000 spins, the lowest energy configuration has a total energy of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; E = - DNJ = - 3 \times 1000 \times J = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By flipping one spin in this system, there will be an energy change of +12J. Therefore the total energy after flipping will be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; -3000J + 12J = -2988J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As there are 1000 spins in the system, any one of these can be flipped. Therefore, the multiplicity of this new configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega = \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = \frac{1000!}{999! 1!} = 1000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, there are 2 degenerate states (999 up, 1 down and 1 up, 999 down), and so the total multiplicity is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Omega_\alpha = g_\alpha \frac{N!}{n_{\uparrow}! n_{\downarrow}!} = 2 \times 1000 = 2000 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, the entropy of this state is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S_\alpha = k_b \ln(\Omega_\alpha) = k_b \ln(2000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The change in entropy between this state and the lowest energy configuration is therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \Delta S = S_f - S_i = k_b \ln(2000) - k_b \ln(2) = k_b \ln\Bigg(\frac{2000}{2}\Bigg) = k_b \ln(1000) = 9.5371821 \times 10^{-23} JK^{-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Magnetisation, M, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[7]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where s&amp;lt;sub&amp;gt;&amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt;&amp;lt;/sub&amp;gt; is the spin of a given spin &amp;lt;i&amp;gt;i&amp;lt;/i&amp;gt; ∈ &amp;lt;i&amp;gt;N&amp;lt;/i&amp;gt;. From the lattices in figure 1, the respective magnetisations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: M = 3\times(+1)  + 2\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: M = 13\times(+1) + 12\times(-1) = 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Free Energy of a system is always looking to be minimised, i.e. as low as possible. The Helmholtz Free energy, F, is given by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[8]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where U is the internal energy, T is the temperature, and S is the entropy. When &amp;lt;math&amp;gt;T=0&amp;lt;/math&amp;gt;, the system is in its lowest energy state. The entropy of this state is &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt;. However, the entropic contribution to the free energy is 0 &amp;lt;math&amp;gt; (T\times S=0) &amp;lt;/math&amp;gt;. Therefore, the only contribution to the free energy is the internal energy, which we know is minimised when all spins are parallel. They can either all be pointing up or all be pointing down, and so for a system with &amp;lt;math&amp;gt; D=3, N=1000&amp;lt;/math&amp;gt;, the magnetisation is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; M = \sum_i^N s_i = \pm \sum_i^{1000} 1 = \pm 1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times (in fact, we are working in reduced units in which &amp;lt;math&amp;gt;J=k_b&amp;lt;/math&amp;gt;, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To calculate the energy, the sum of the spin of every site multiplied by the spin of each of its neighbours is taken, as per [2]. As the lattice is formed using a numpy array, this calculation can be performed using a nested loop to scan through each spin in the lattice. Using indexing, the neighbours of a given spin can be selected, and [2] can be applied. The following function shows how this was implemented.    &lt;br /&gt;
&lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Returns the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     energy=0&lt;br /&gt;
     for i in range(0,len(self.lattice)):&lt;br /&gt;
         for j in range(0,len(self.lattice[i])):&lt;br /&gt;
             s0=self.lattice[i][j]&lt;br /&gt;
             s1=self.lattice[i][(j+1)%self.n_cols]&lt;br /&gt;
             s2=self.lattice[i][j-1]&lt;br /&gt;
             s3=self.lattice[(i+1)%self.n_rows][j]&lt;br /&gt;
             s4=self.lattice[i-1][j]&lt;br /&gt;
             energy=energy+s0*s1+s0*s2+s0*s3+s0*s4&lt;br /&gt;
     return -0.5*energy&lt;br /&gt;
&lt;br /&gt;
A similar approach was used to calculate the magnetisation. Magnetisation is found from [7], so by scanning through each spin in the lattice and keeping a running sum, this can be calculated. The following function shows how this was implemented. &lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     magnetisation=0&lt;br /&gt;
     for i in self.lattice:&lt;br /&gt;
        for j in i:&lt;br /&gt;
             magnetisation=magnetisation+j&lt;br /&gt;
     return magnetisation&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116 ILcheck img.png|700px|thumb|right|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: The result of running the ILcheck.py script - as shown, the actual values of the energy and magnetisation match the expected values.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Run the ILcheck.py script from the IPython Qt console using the command  &amp;lt;pre&amp;gt;%run ILcheck.py&amp;lt;/pre&amp;gt; The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When running the ILcheck.py script, the three lattices in figure 3 were produced. These show the lowest energy, random, and highest energy configurations of a 4x4 Ising Lattice, and their expected energies. The energies and magnetisations calculated using the functions written above match the expected values, showing that they work!&lt;br /&gt;
&lt;br /&gt;
=Introduction to Monte Carlo simulation=&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
In statistical mechanics, average value of a property of a system at a given temperature is computed using the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle X \rangle _T = \sum_\alpha X_\alpha \rho (\alpha)&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[9]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\rho(\alpha)&amp;lt;/math&amp;gt; is the probability of the system being in the state &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. The states in the Ising Model are distributed via a Boltzmann distribution, and therefore, the average values of energy and magnetisation are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E \rangle _T = \frac{1}{Z} \sum_\alpha E_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[10]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle M \rangle _T = \frac{1}{Z} \sum_\alpha M_\alpha \exp \left\{ -\frac{E_\alpha}{k_bT} \right\} &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[11]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where Z is the partition function and &amp;lt;math&amp;gt;E_\alpha&amp;lt;/math&amp;gt; is the energy of a given configuration, &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;. Although these equations are the definition of the average energy and magnetisation, they are not practical. The partition functions for the 1D and 2D Ising lattices are given below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 1D: Z\left(T, N\right) = \bigg[ 2 \cosh \left( \frac{J}{k_b T} \right) \bigg] ^N&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[12]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; 2D: \lim_{N \rightarrow \infty} \ln Z\left(T, N\right) = \ln \left(2 \cosh\left( 2 \beta J\right) \right) + \frac{1}{2 \pi} \int_0^{\pi} \ln  \frac{1}{2} \left( 1 + \sqrt{1 - \kappa ^2 \sin ^2 \phi} \right) d\phi &amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[13]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt; \kappa = \frac{2 \sinh \left( 2\beta J\right)}{\cosh ^2 \left( 2\beta J \right)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For dimensions greater than 2, no analytical solutions are known! Using these to compute the average energies and magnetisations of Ising Lattices both lengthens and complicates the process, and so the problem is tackled using numerical methods, namely the Monte Carlo simulation. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: How many configurations are available to a system with 100 spins? To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each spin, there are 2 possible configuration, either spin up or spin down. If there were 100 spins in a system, the total number of configurations available for that system would be 2&amp;lt;sup&amp;gt;100&amp;lt;/sup&amp;gt; = 1267650600228229401496703205376 configurations. Assuming a computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; of these configurations per second, a single value of &amp;lt;math&amp;gt;\langle M \rangle _T&amp;lt;/math&amp;gt; would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{2^{100}}{10^9} = 1.2676506002282295 \times 10^{21} s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To put this into perspective, this is equivalent to approx. 40196937 million years! This is obviously not a practical solution. Instead, we can consider [10] &amp;amp; [11]. The majority of the states in the system will have a very small Boltzmann weighting factor &amp;lt;math&amp;gt;\exp \left\{ E_\alpha/k_b T\right\}&amp;lt;/math&amp;gt; and so will not contribute much to the overall average energy. Instead, if only the states with sizeable Boltzmann weighting factors are considered, then an enormous amount of time can be saved. This is &amp;lt;i&amp;gt;importance sampling&amp;lt;/i&amp;gt; - instead of sampling through all the possible states, only the states which the system are likely to occupy are sampled.&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Implement a single cycle of the Monte Carlo algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;\langle E \rangle, \langle E^2 \rangle, \langle M \rangle, \langle M^2 \rangle &amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Metropolis Monte Carlo algorithm is as follows: &lt;br /&gt;
&lt;br /&gt;
# Start from a given configuration of spins, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt;, with energy &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Choose a single spin &#039;&#039;&#039;at random&#039;&#039;&#039;, and &amp;quot;flip&amp;quot; it, to generate a new configuration &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy of this new configuration, &amp;lt;math&amp;gt;E_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Calculate the energy difference between the states, &amp;lt;math&amp;gt;\Delta E = E_1 - E_0&amp;lt;/math&amp;gt;&lt;br /&gt;
## If the &amp;lt;math&amp;gt;\Delta E &amp;lt; 0&amp;lt;/math&amp;gt; (the spin flipping decreased the energy), then we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
##* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
## If &amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt;, the spin flipping increased the energy. By considering the probability of observing the starting and final states, &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\alpha_1&amp;lt;/math&amp;gt;, it can be shown that the probability for the transition between the two to occur is &amp;lt;math&amp;gt;\exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;. To ensure that we only accept this kind of spin flip with the correct probability, we use the following procedure:&lt;br /&gt;
### Choose a random number, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, in the interval &amp;lt;math&amp;gt;[0,1)&amp;lt;/math&amp;gt;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R \leq \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;accept&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* We set &amp;lt;math&amp;gt;\alpha_0 = \alpha_1&amp;lt;/math&amp;gt;, and &amp;lt;math&amp;gt;E_0 = E_1&amp;lt;/math&amp;gt;, and then &#039;&#039;&#039;go to step 5&#039;&#039;&#039;&lt;br /&gt;
### If &amp;lt;math&amp;gt;R &amp;gt; \exp \left\{-\frac{\Delta E}{k_BT}\right\}&amp;lt;/math&amp;gt;, we &#039;&#039;&#039;reject&#039;&#039;&#039; the new configuration.&lt;br /&gt;
###* &amp;lt;math&amp;gt;\alpha_0&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E_0&amp;lt;/math&amp;gt; are left unchanged. &#039;&#039;&#039;Go to step 5&#039;&#039;&#039;&lt;br /&gt;
# Update the running averages of the energy and magnetisation.&lt;br /&gt;
# Monte Carlo &amp;quot;cycle&amp;quot; complete, &#039;&#039;&#039;return to step 2&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the function below. There are three possible routes in this algorithm:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;lt;0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;accepted&#039;&#039;&#039;&lt;br /&gt;
#&amp;lt;math&amp;gt;\Delta E &amp;gt; 0&amp;lt;/math&amp;gt; and the new configuration is &#039;&#039;&#039;rejected&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Since routes 1 and 2 end in the same result, and only route 3 ends in a rejection of the new configuration, only one &#039;if&#039; statement is required. This can be seen in the code below. Once the new state is either accepted or rejected, the energy, energy squared, magnetisation and magnetisation squared of the new state is added to the variables defined in the IsingLattice constructor also shown below:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0   &lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one step of the Monte Carlo simulation&amp;quot;&lt;br /&gt;
     self.n_cycles+=1   #Increases the counter recording the number of Monte Carlo steps performed &lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines selects the coordinates of a random spin in the lattice&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     #the following line flips the randomly selected spin &lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line is the condition for which the new configuration is rejected&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_i&lt;br /&gt;
         self.E2=self.E2+e_i**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.E=self.E+e_f&lt;br /&gt;
         self.E2=self.E2+e_f**2&lt;br /&gt;
         self.M=self.M+float(mag)&lt;br /&gt;
         self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
After a set of Monte Carlo steps, the E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M and M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; values from each step will have been summed up. To calculate the average quantities, the values are divided by the number of Monte Carlo steps taken. The statistics() function below shows this calculation.   &lt;br /&gt;
&lt;br /&gt;
  def statistics(self):&lt;br /&gt;
      &amp;quot;Returns the average E, E^2, M, M^2 and the number of Monte Carlo steps performed&amp;quot; &lt;br /&gt;
      E=self.E/self.n_cycles&lt;br /&gt;
      E2=self.E2/self.n_cycles&lt;br /&gt;
      M=self.M/self.n_cycles&lt;br /&gt;
      M2=self.M2/self.n_cycles&lt;br /&gt;
      return E,E2,M,M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This algorithm enables the minimisation of free energy, despite using just the calculated internal energy. However, as shown in [8], there is also an entropic contribution to the free energy. So how is this method accounting for the entropy contribution?&lt;br /&gt;
&lt;br /&gt;
By involving the Boltzmann factor as the probability factor, this means the accepted states are distributed via the Boltzmann distribution. By randomly flipping a spin, there is a probability that a higher energy state can be accepted. By accepting this higher energy state, it enables fluctuations about the equilibrium state. The underlying distribution of these states around the equilibrium state would be a Gaussian. Being able to access these states accounts for the entropy. An easy way to see this is by considering the system at a very high temperature. As T increases, more and more configurations become accessible, and the Boltzmann distribution flattens. Consequently, the multiplicity &amp;lt;math&amp;gt;\Omega \rightarrow 2^N&amp;lt;/math&amp;gt;, and hence &amp;lt;math&amp;gt;S \rightarrow k_b \ln(2^N)&amp;lt;/math&amp;gt;, where N is the number of spins, as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;. As a Monte Carlo step is applied on the system, it is significantly more likely for a higher energy configuration to be accepted at a high temperature, because the Boltzmann factor &amp;lt;math&amp;gt; \exp \left\{ \frac{E_\alpha}{k_b T} \right\} \rightarrow 1&amp;lt;/math&amp;gt; as &amp;lt;math&amp;gt;T \rightarrow \infty&amp;lt;/math&amp;gt;, which corresponds with the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;, of the system, and hence, the entropy. Thus, this method accounts for the entropy.  &lt;br /&gt;
 &lt;br /&gt;
[[File:JGH116LowEnergyGif.gif|300px|thumb|right|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 0.5, producing: a) a lowest energy configuration of the lattice b) a metastable state]]&lt;br /&gt;
[[File:JGH116Hightempanim.png|300px|thumb|right|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: The result of running the ILanim.py script for an 8x8 lattice at a temperature of 15, producing fluctuations about E,M=0]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Curie Temperature is defined as the temperature above which a material loses their permanent magnetic properties. Therefore, below the Curie Temperature, i.e. &amp;lt;math&amp;gt; T &amp;lt; T_C&amp;lt;/math&amp;gt; spontaneous magnetisation is expected. &lt;br /&gt;
&lt;br /&gt;
The animation script &amp;lt;code&amp;gt;ILanim.py&amp;lt;/code&amp;gt; was run for an 8x8 lattice at a temperature of 0.5. Once the simulation stopped changing energy, i.e. once it had reached an equilibrium state, a screenshot of the graph and the averaged values was taken. &lt;br /&gt;
&lt;br /&gt;
Each simulation begins with a random lattice, and performs Monte Carlo steps on it. The first simulation performed is shown in figure 4. This shows the system lowering its energy with each Monte Carlo step, reaching the lowest energy configuration at around step 700. Once the system has reached this configuration, it does not fluctuate from it, and it remains there, i.e. it has reached equilibrium. The energy per spin in the lowest energy configuration is -2, and the magnetisation per spin is 1. The averaged quantities, however, do not discard the initial minimisation steps, and so takes them into account when calculating the average. This is why the averaged quantities for E and M are significantly different to -2 and 1 respectively. &lt;br /&gt;
&lt;br /&gt;
The simulation was run again under the same conditions, with an 8x8 lattice at a temperature of 0.5. This time, however, the system reached an equilibrium in a metastable state (see figure 5). This is when the system is kinetically stable, but not in its lowest energy state. Instead, it is stuck in a local minimum instead of the global minimum. The mechanical &#039;thermal&#039; fluctuation applied by the Monte Carlo algorithm is not enough for the system to kick itself out of this state. However, if a stronger &#039;kick&#039; is applied, the system will free itself from this metastable state. Therefore, despite being stable, the system is not in equilibrium. Examples of metastable states in reality are Diamond, or supercooled water. In this system, there is still an overall magnetisation, as there are more spin up spins than spin down spins. &lt;br /&gt;
&lt;br /&gt;
The simulation was run yet again with the same lattice size, but at a much higher temperature of 15 to ensure &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;. It is immediately obvious that at a high temperature there are significantly more fluctuations (see figure 6). However, these fluctuations are all around an &#039;equilibrium&#039; of &amp;lt;math&amp;gt;E,M=0&amp;lt;/math&amp;gt;. As described above, the states around the equilibrium state are distributed via a Gaussian distribution. As a consequence of this distribution, the magnitude of these fluctuations from the equilibrium is estimated to be &amp;lt;math&amp;gt; \approx \dfrac{1}{\sqrt N}&amp;lt;/math&amp;gt;. As &amp;lt;math&amp;gt;N = 64&amp;lt;/math&amp;gt; here, the magnitude of the fluctuation would be approximately &amp;lt;math&amp;gt;1/8 = 0.125&amp;lt;/math&amp;gt;. As seen in figure 6, this seems to be the case, validating the claim that there is an underlying Gaussian distribution. &lt;br /&gt;
&lt;br /&gt;
The fluctuations in magnetisation here are all happening around &amp;lt;math&amp;gt;M=0&amp;lt;/math&amp;gt;. It is therefore reasonable to assume that a temperature of 15 is greater than the Curie temperature. It is possible to conclude that when &amp;lt;math&amp;gt;T&amp;gt;T_C, \langle M \rangle \rightarrow 0&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
By performing these three simulations, we show that when &amp;lt;math&amp;gt;T&amp;lt;T_C&amp;lt;/math&amp;gt;, there is spontaneous magnetisation, and when &amp;lt;math&amp;gt;T&amp;gt;T_C&amp;lt;/math&amp;gt;, the system loses most, if not all of its magnetic properties.&lt;br /&gt;
&lt;br /&gt;
===Accelerating the code===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;current&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The python script &amp;lt;code&amp;gt;ILtimetrial.py&amp;lt;/code&amp;gt; was run 16 times, giving the following runtimes for 2000 Monte Carlo steps (to 12 d.p.):&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|5.75730054321||5.81583604945||5.72269787654||6.06356069136||5.69132167910||5.99814558025||5.75052444444||5.59712908642&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
||5.77942953086||5.84957432099||6.40311703704||5.43301412346||5.69427753156||6.01423604938||5.88791506173||5.89307417284&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 5.83444711111 \pm 0.21330451356  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Look at the documentation for the [http://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html NumPy sum function]. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy [http://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html roll] and [http://docs.scipy.org/doc/numpy/reference/generated/numpy.multiply.html multiply] functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.sum()&amp;lt;/code&amp;gt; function sums all the elements in an array. As the calculation for magnetisation, M, involves summing up all the spins, this sum function can be applied to the lattice, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def magnetisation(self):&lt;br /&gt;
     &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
     return 1.0*np.sum(self.lattice)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;numpy.multiply()&amp;lt;/code&amp;gt; function multiplies each element in an array with the corresponding element in another array. The &amp;lt;code&amp;gt;numpy.roll()&amp;lt;/code&amp;gt; function enables the shifting of rows up and down and columns left and right in the array. By combining these two functions together, as well as the sum function, it is possible to calculate the energy of the lattice. Multiplying the lattice by a lattice rolled once to the right takes into account all interactions between each spin and its neighbour to the left. Multiplying the lattice by a lattice rolled once downwards takes into account all interactions between each spin and its neighbour above. This counts every interaction in the lattice without double counting. By applying the sum function to these lattices, and adding the resulting sums together, you calculate the energy. The code for this is shown below:&lt;br /&gt;
 &lt;br /&gt;
 def energy(self):&lt;br /&gt;
     &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
     il=self.lattice&lt;br /&gt;
     return -1.0*np.sum(np.multiply(il, np.roll(il, 1, 0)))-1.0*np.sum(np.multiply(il, np.roll(il, 1, 1)))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use the script ILtimetrial.py to record how long your &#039;&#039;new&#039;&#039; version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After implementing these new functions for energy and magnetisation, the runtime was shortened significantly, as shown by the following table:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!&lt;br /&gt;
!1&lt;br /&gt;
!2&lt;br /&gt;
!3&lt;br /&gt;
!4&lt;br /&gt;
!5&lt;br /&gt;
!6&lt;br /&gt;
!7&lt;br /&gt;
!8&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.514644938272||0.3674540246914||0.3432410864198|| 0.397299753086||0.3896584691358||0.342273185185||0.3765925925925||0.325619753086&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
!9&lt;br /&gt;
!10&lt;br /&gt;
!11&lt;br /&gt;
!12&lt;br /&gt;
!13&lt;br /&gt;
!14&lt;br /&gt;
!15&lt;br /&gt;
!16&lt;br /&gt;
|-&lt;br /&gt;
!Runtime (s)&lt;br /&gt;
|0.3554710123456||0.327868049383||0.3836053333332||0.4080521481483||0.3602054320988||0.317112888889||0.358967703704||0.339137975309&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From these values, the average runtime for 2000 Monte Carlo steps is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t = 0.369200271605 \pm 0.0455071720835  s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is almost 16 times faster than the previous code!&lt;br /&gt;
&lt;br /&gt;
=Phase Behaviour of the Ising Model=&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116LatticeSizegif.gif|thumb|300px|&#039;&#039;&#039;Figure 6&#039;&#039;&#039; A gif showing how the number of Monte Carlo steps required for equilibrium increases with lattice size.]]&lt;br /&gt;
[[File:JGH116TempGif.gif|thumb|300px|&#039;&#039;&#039;Figure 7&#039;&#039;&#039; A gif showing how the energy and magnetisation as functions of number of Monte Carlo steps vary with temperature.]]&lt;br /&gt;
[[File:JGH116Spinodal decomposition.png|thumb|300px|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figure 6 shows the result of running the python script &amp;lt;code&amp;gt;ILfinalframe.py&amp;lt;/code&amp;gt; with increasing lattice sizes. It can be seen that the number of Monte Carlo steps required for the system to reach equilibrium increases with lattice size. For an 8x8 system, only around 400 steps are required. For a 15x15 system, this increased to around 5000 steps. When the lattice size was increased to 30x30 and 50x50, this increased rapidly to 750000 and 950000 steps respectively.&lt;br /&gt;
&lt;br /&gt;
Figure 7 shows the result of running the python script ILfinalframe.py with an 8x8 lattice and increasing temperature. As the temperature increases, the energy and magnetisation begin to fluctuate about the lowest energy. Once T=2, the fluctuations begin to increase even further. At T=3 &amp;amp; T=5, the energy fluctuates around E,M=0. This shows that the Curie Temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, is between 2 and 3 for the 8x8 system.&lt;br /&gt;
&lt;br /&gt;
The code was run again for a 100x100 system at a temperature of 0.01 with 1 million Monte Carlo steps. The result of this simulation is shown in figure 8. Similar to the metastable state described above, there are defined domains of parallel spins, which have a net magnetisation.   &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
WRITE SOMETHING ABOUT SPINODAL DECOMPOSITION&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
From above, it can be seen that the number of steps taken to reach equilibrium varies with lattice size and temperature. If the averaging code were to be adapted to start recording data to average after &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; number of steps, it would not be possible to state a number &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; that applies for all situations, i.e. as stated before, &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; varies depending on temperature and lattice size. In order to get around finding this relationship between &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;, lattice size and temperature, general conditions for equilibrium were considered. In any system, equilibrium occurs when the system is stable in a global minimum. As seen in previous figures, this is when the average energy remains constant. To find the point at which this occurs, the initial algorithm used was as follows:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a sample of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; is taken, i.e. data from the point &amp;lt;math&amp;gt;a-w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; is taken as the sample.&lt;br /&gt;
#The standard deviation of this sample is taken:&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
 &lt;br /&gt;
This algorithm worked for small lattices and low temperatures. However, at high temperature, even though the system had reached equilibrium, the energy fluctuated significantly more than our standard deviation threshold would allow. Therefore, the algorithm had to be adapted to account for these fluctuations. Rather than taking the standard deviation of a single sample of data, the standard deviation of the means of three samples was taken. The initial sample size in the algorithm was split into three separate samples, and the mean of each of these samples were taken. The standard deviation of these means was measured, and if this was below a predefined &#039;threshold&#039; value the system would be defined as in equilibrium. The algorithm was changed to reflect this:&lt;br /&gt;
&lt;br /&gt;
#Set a &#039;checking&#039; variable as &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; at the beginning of the simulation - once the system has reached equilibrium, this will be changed to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;.&lt;br /&gt;
#Once enough steps have been taken at a point &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt;, a three samples of data of size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; are taken, i.e. three samples from the point &amp;lt;math&amp;gt;a-3w&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; are taken, each with size &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The means of these three samples are taken, and then the standard deviation of these means is calculated.&lt;br /&gt;
#*If this standard deviation is lower than the predefined &#039;threshold&#039; value, the system is in equilibrium, and the checking variable is set to &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt;, a new step counter is started, and the relevant statistics variables begin to record data with the subsequent Monte Carlo steps. &lt;br /&gt;
#*If this standard deviation is higher than the predefined &#039;threshold&#039; value, checking variable remains set to &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;, the next Monte Carlo step is taken and the sample moves along.&lt;br /&gt;
&lt;br /&gt;
This algorithm was implemented in the &amp;lt;code&amp;gt;montecarlostep()&amp;lt;/code&amp;gt; function, as shown below:&lt;br /&gt;
&lt;br /&gt;
 def montecarlostep(self, T):&lt;br /&gt;
     &amp;quot;Performs one Monte Carlo step&amp;quot;&lt;br /&gt;
     self.n_cycles+=1&lt;br /&gt;
     e_i = self.energy()&lt;br /&gt;
     #the following two lines select the coordinates of a random spin&lt;br /&gt;
     random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
     random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
     self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
     e_f=self.energy()&lt;br /&gt;
     random_number = np.random.random()&lt;br /&gt;
     #the following line defines the sample size&lt;br /&gt;
     weight=self.n_rows*self.n_cols*5&lt;br /&gt;
     #this &#039;if&#039; statement performs the equilibrium check, only if the system is not in equilibrium &lt;br /&gt;
     if self.n_cycles&amp;gt;3*weight and self.check==False:&lt;br /&gt;
         mean1=np.mean(np.array(self.elist[self.n_cycles-3*weight:self.n_cycles-2*weight]))&lt;br /&gt;
         mean2=np.mean(np.array(self.elist[self.n_cycles-2*weight:self.n_cycles-weight]))&lt;br /&gt;
         mean3=np.mean(np.array(self.elist[self.n_cycles-weight:self.n_cycles]))&lt;br /&gt;
         sample=np.array([mean1,mean2,mean3])  &lt;br /&gt;
         if np.std(sample)&amp;lt;0.01*T: #if the standard deviation is smaller than this temp. dependent threshold variable, the system is in equilibrium&lt;br /&gt;
             self.check=True   #redefine the checking variable to show the system is in equilibrium&lt;br /&gt;
             print(self.n_cycles)&lt;br /&gt;
     if e_f&amp;gt;e_i and random_number &amp;gt; np.exp(-(e_f-e_i)/T):&lt;br /&gt;
         self.lattice[random_i][random_j]=-1*self.lattice[random_i][random_j]&lt;br /&gt;
         self.elist.append(e_i)&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         #This &#039;if&#039; statement is added so that the statistics variables will only start recording data when the system is in equilibrium&lt;br /&gt;
         if self.check==True: &lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_i&lt;br /&gt;
             self.E2=self.E2+e_i**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_i), float(mag)&lt;br /&gt;
     else:&lt;br /&gt;
         mag=self.magnetisation()&lt;br /&gt;
         self.elist.append(e_f)&lt;br /&gt;
         self.mlist.append(mag)&lt;br /&gt;
         if self.check==True:&lt;br /&gt;
             self.n_threshold+=1&lt;br /&gt;
             self.E=self.E+e_f&lt;br /&gt;
             self.E2=self.E2+e_f**2&lt;br /&gt;
             self.M=self.M+float(mag)&lt;br /&gt;
             self.M2=self.M2+float(mag)**2&lt;br /&gt;
         return float(e_f), float(mag)&lt;br /&gt;
&lt;br /&gt;
In order to get this code to work, new variables had to be defined in the &amp;lt;code&amp;gt;__init__()&amp;lt;/code&amp;gt; function:&lt;br /&gt;
&lt;br /&gt;
 def __init__(self, n_rows, n_cols):&lt;br /&gt;
     self.n_rows = n_rows&lt;br /&gt;
     self.n_cols = n_cols&lt;br /&gt;
     self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
     self.E=0&lt;br /&gt;
     self.E2=0&lt;br /&gt;
     self.M=0&lt;br /&gt;
     self.M2=0&lt;br /&gt;
     self.n_cycles=0&lt;br /&gt;
     self.check=False&lt;br /&gt;
     self.elist=[]&lt;br /&gt;
     self.mlist=[]&lt;br /&gt;
     self.n_threshold=0&lt;br /&gt;
&lt;br /&gt;
The statistics function needed to be altered as well in order to account for this - instead of dividing each sum by &amp;lt;code&amp;gt;self.n_cycles&amp;lt;/code&amp;gt;, we instead divide by the new counter that begins when the system is defined as being in equilibrium, &amp;lt;code&amp;gt;self.n_threshold&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def statistics(self):   &lt;br /&gt;
     E=self.E/self.n_threshold&lt;br /&gt;
     E2=self.E2/self.n_threshold&lt;br /&gt;
     M=self.M/self.n_threshold&lt;br /&gt;
     M2=self.M2/self.n_threshold&lt;br /&gt;
     return E,E2,M,M2,self.n_threshold&lt;br /&gt;
 &lt;br /&gt;
This algorithm proved to work well when running simulations. RUN EXAMPLES&lt;br /&gt;
&lt;br /&gt;
===The effect of temperature===&lt;br /&gt;
[[File:JGH1168x8data.png|thumb|400px|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;]]&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8 x 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the python script &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; performs a series of Monte Carlo steps for increasing temperatures, enabling the plotting of average values with temperature. These plots for an 8x8 system are shown in figure 9. The temperature range was from 0.25 to 5.0, with a temperature spacing of 0.01. This spacing was chosen in order to show the detail, and the level of fluctuation that occurs when criticality is reached. &lt;br /&gt;
&lt;br /&gt;
At low temperatures, the preferred state for the system is the lowest energy state, which has &amp;lt;math&amp;gt;\langle E \rangle = -2&amp;lt;/math&amp;gt;,&amp;lt;math&amp;gt; \langle M \rangle = \pm 1&amp;lt;/math&amp;gt;, i.e. all spins are parallel. As the temperature rises, the state has a net magnetisation, but clusters of opposite spins begin to appear. These clusters have an intrinsic size which increases with temperature, called the correlation length. As the clusters grow, they start to contain smaller, fractal clusters within themselves. As the temperature rises above 2, the system starts to undergo a phase transition, and it reaches the critical point, the Curie Temperature &amp;lt;math&amp;gt;(T_C)&amp;lt;/math&amp;gt;. At this point, the correlation length diverges, and the entire system turns into a giant cluster, with no net magnetisation. This giant cluster contains smaller sized &#039;fractal&#039; clusters. While a single perturbation may not affect a large cluster, it can affect the smaller clusters. However, when the smaller clusters are perturbed, they in turn perturb a larger cluster, which in turn perturbs an even larger cluster and so on. Therefore, a small perturbation can greatly affect a system at its critical point. This can be seen in the graphs plotted for energy and magnetisation. The large fluctuations in the average magnetisation show the system approaching its critical point, and once past it, the average magnetisation is zero. A similar observation can be made for the energy, however it is not as pronounced - there are fluctuations in the energy close to the critical point. &lt;br /&gt;
&lt;br /&gt;
Error bars are added to the graphs to give a sense of how much the average energy/magnetisation fluctuates at a given temperature. As expected, the error in energy increases as temperature rises..........................................CONTINUE&lt;br /&gt;
&lt;br /&gt;
===The effect of system size===&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116energif.gif|thumb|400px|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;]]&lt;br /&gt;
[[File:JGH116Maggif.gif|thumb|400px|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Figures 10 and 11 show how the variation of energy and magnetisation with temperature varies with lattice size. The temperature range used in &amp;lt;code&amp;gt;ILtemperaturerange.py&amp;lt;/code&amp;gt; for all these lattices was from 0.25 to 5.0, with a temperature spacing of 0.01, for the same reasons as before. The first major observation is that the size of the error bars decrease as the lattice gets larger. This is intuitive, as a single spin flip for a larger system with more spins is less likely to affect the average energy than it would for a smaller system. &lt;br /&gt;
&lt;br /&gt;
Secondly, the.............&lt;br /&gt;
&lt;br /&gt;
It is a characteristic of phase transitions that large fluctuations in the system occur over long ranges. This can be seen as the lattice size increases from 2x2 → 16x16. The magnetisation begins to fluctuate drastically at a much lower temperature than the Curie temperature for the 2x2 system, and the temperature at which this fluctuation occurs increases as the lattice size increases. However, the size of the fluctuation also decreases in size as lattice size increases. In the 32x32 lattice simulation, fluctuations only begin to occur once the phase transition begins. Therefore, a lattice size of 16x16 is large enough to capture the long range fluctuations.&lt;br /&gt;
&lt;br /&gt;
===Calculating the heat capacity===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: By definition,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this, show that&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;.)&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The average energy ⟨E⟩ is defined by the following equation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \sum_n E_n \rho (n) = \frac{\sum_n E_n e^{-\beta E_n}}{\sum_n e^{-\beta E_n}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This can be manipulated to give the average energy as a function of the partition function, Z:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E\rangle = \frac{1}{Z} \sum_n E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \sum_n -\frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial}{\partial \beta}\sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\frac{\partial Z}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⟨E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;⟩ can be found using a similar method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\langle E^2 \rangle = \frac{1}{Z} \sum_n {E_n}^2 e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z} \sum_n E_n \frac{\partial}{\partial \beta} e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n \frac{\partial}{\partial \beta} E_n e^{-\beta E_n}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{Z}\sum_n -\frac{\partial }{\partial \beta}\bigg[\frac{\partial}{\partial \beta} e^{-\beta E_n}\bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{Z} \frac{\partial ^2}{\partial \beta ^2} \sum_n e^{-\beta E_n}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \langle E^2 \rangle = \frac{1}{Z} \frac{\partial^2 Z}{\partial \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Variance of a sample is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;Var\big[ X\big] = \langle X^2 \rangle - \langle X \rangle ^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the definitions defined above, as well as the definition of heat capacity, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt;, the heat capacity can be expressed in terms of the variance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;C_v =\frac{\partial \left \langle E \right\rangle}&lt;br /&gt;
{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \beta}{\partial T}\frac{\partial \langle E\rangle}{\partial \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{\partial \frac{1}{k_b T}}{\partial T}\Bigg[\frac{\partial \langle E\rangle}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = -\frac{1}{k_b T^2}\frac{\partial}{\partial \beta}\Bigg[-\frac{1}{Z}\frac{\partial Z}{\partial \beta}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial}{\partial \beta}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[\bigg[\frac{\partial Z}{\partial \beta}\frac{\partial}{\partial Z}\frac{1}{Z}\bigg]\frac{\partial Z}{\partial \beta} + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Bigg[-\frac{1}{Z^2} \bigg(\frac{\partial Z}{\partial \beta}\bigg)^2 + \frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2}\Bigg] = \frac{1}{k_b T^2}\Bigg[\frac{1}{Z}\frac{\partial^2 Z}{\partial \beta^2} -\bigg(\frac{1}{Z} \frac{\partial Z}{\partial \beta}\bigg)^2\Bigg]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{1}{k_b T^2}\Big[\langle E^2 \rangle - \langle E \rangle ^2\Big]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = \frac{Var[E]}{k_b T^2}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[14]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As shown in [14], there is a relationship between the heat capacity, variance and the temperature. Before any other code was written, a function to determine the heat capacity from these two variables was written. &lt;br /&gt;
&lt;br /&gt;
The previously defined &amp;lt;code&amp;gt;statistics()&amp;lt;/code&amp;gt; function returns five values: &amp;lt;math&amp;gt;\langle E \rangle , \langle E^2 \rangle , \langle M \rangle , \langle M^2 \rangle&amp;lt;/math&amp;gt; and the number of cycles. The variance in energy can be calculated from these values:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; Var[E] = \langle E^2 \rangle - \langle E \rangle ^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As &amp;lt;math&amp;gt;Var[E]&amp;lt;/math&amp;gt; is in units of &amp;lt;math&amp;gt;k_b^2&amp;lt;/math&amp;gt;, and our temperature is unitless, &amp;lt;math&amp;gt;k_b&amp;lt;/math&amp;gt; can be removed from [14], changing the equation to define to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; C_v = \frac{Var[E]}{T^2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In python, this is written as:&lt;br /&gt;
&lt;br /&gt;
 def C_v(var, T):&lt;br /&gt;
     &amp;quot;Calculates the heat capacity from the variance and temperature&amp;quot;&lt;br /&gt;
     return var/(T**2)&lt;br /&gt;
&lt;br /&gt;
[[File:JGH116heatcapgif.gif|thumb|400px|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
The heat capacity was plotted for all the lattice sizes, and this is shown in figure 12. The main observation to note is the peak in heat capacity rises and sharpens as lattice size increases. A peak in the heat capacity corresponds to a phase transition. Therefore, the peak in the heat capacity should correspond to the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
However, in this system, we expect to see a first order phase transition, which corresponds to a divergence in the heat capacity at the Curie temperature (as proven by Lars Onsager). We do not see this divergence in the heat capacity plot. This is due to finite size effects. For a finite system, with a lattice size &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, we see rounded peaks. As &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; increases, the peak grows in height and narrows, but only as &amp;lt;math&amp;gt;L \rightarrow \infty &amp;lt;/math&amp;gt;, we see a true first order phase transition, i.e. a divergence in heat capacity, at &amp;lt;math&amp;gt;T=T_C&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
It is possible to correct for these finite size effects, and to calculate the Curie temperature for an infinite lattice (i.e. the temperature at which a true first order phase transition occurs). It can be shown that the temperature, &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, which yields the maximum in the heat capacity must scale according to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&#039;&#039;&#039;[15]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; is the lattice size, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature for an infinite lattice, and &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant. Therefore, in order to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, we must find &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; for a number of lattice sizes.&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;TASK: A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, M, M&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, C&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which. To do this, you will need to pass the label=&amp;quot;...&amp;quot; keyword to the plot function, then call the legend() function of the axis object (documentation [http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.legend here]).&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Running the simulation in C++ allows for much longer runtimes than python, and therefore can produce much more accurate data. The graphs below show a comparison between the 16x16 lattice data produced by the C++ program and the python program. &lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Energy16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13a&#039;&#039;&#039; Average Energy &amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Mag16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13b&#039;&#039;&#039; Average Magnetisation&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Heatcap16Ccomp.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 13c&#039;&#039;&#039; Heat Capacity&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At a first glance, we can see that they have a lot of similarities. The average energy graph (figure 13a) fits perfectly with the C++ data. The magnetisation (figure 13b), however, fluctuates significantly more than the C++ data. This is most likely due to shorter runtimes for the python simulation. The shorter the runtime, the more fluctuations will be visible in the critical region. &lt;br /&gt;
&lt;br /&gt;
The heat capacity (figure 13c) also fits fairly well with the C++ data. Even with the shorter runtimes, the curve still follows the shape of the C++ data. The height of the peak, however, does not fit well - this can also be attributed to shorter runtimes and the fluctuations in the system. The peak, however, still occurs at the same temperature, which is important, as we want to use this data to calculate the Curie temperature for an infinite lattice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A python function was written to read a file containing the data for one of the simulation, extract it, and plot it alongside a fitted polynomial. &lt;br /&gt;
&lt;br /&gt;
 def plot_and_fit(FILE, degree):&lt;br /&gt;
 &amp;quot;Extracts the Heat Capacity data from a file and plots it against a polynomial fit&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     fit=np.polyfit(T,C,degree)&lt;br /&gt;
     T_min = np.min(T)&lt;br /&gt;
     T_max = np.max(T)&lt;br /&gt;
     T_range = np.linspace(T_min, T_max, 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By varying the degree of the fitted polynomial, it was easily possible to improve the fit. Figures 14 and 15 show how the fit improves as the degree of the fitted polynomial increases. If the .gif files do not show, click on the thumbnails to view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Fittinggif.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 14&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice. Once the polynomial reaches a degree of 10 the fit does not improve.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116Output kf2iMO.gif|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 15&#039;&#039;&#039; Heat Capacity vs Temperature for an 8x8 lattice. Once the polynomial reaches a degree of 20 the fit does not improve much.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region. &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following function reads a file containing data for a simulation, extract the heat capacity, and fit a polynomial to the peak in the heat capacity. The region of the graph for which the polynomial fits against is determined by the variables &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; Therefore, if &amp;lt;code&amp;gt;Tmin&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Tmax&amp;lt;/code&amp;gt; are fitted against the peak, a much more accurate value for the coordinates of the peak can be obtained. &lt;br /&gt;
&lt;br /&gt;
 def peak_fit(FILE, degree, Tmin, Tmax):&lt;br /&gt;
     &amp;quot;Plots the heat capacity against a polynomial fit about the peak&amp;quot;&lt;br /&gt;
     data=loadtxt(FILE)&lt;br /&gt;
     T=data[:,0]&lt;br /&gt;
     E=data[:,1]&lt;br /&gt;
     E2=data[:,2]&lt;br /&gt;
     var=E2-E**2&lt;br /&gt;
     C=C_v(var, T)&lt;br /&gt;
     selection = np.logical_and(T&amp;gt;Tmin, T&amp;lt;Tmax)&lt;br /&gt;
     peak_T_values=T[selection]&lt;br /&gt;
     peak_C_values=C[selection]&lt;br /&gt;
     fit=np.polyfit(peak_T_values,peak_C_values,degree)&lt;br /&gt;
     T_range = np.linspace(np.min(T), np.max(T), 1000)&lt;br /&gt;
     fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
     figure=figsize(8,4)&lt;br /&gt;
     plot(T,C, linestyle=none, marker=&#039;o&#039;, markersize=1)&lt;br /&gt;
     plot(T_range, fitted_C_values, label=&#039;Fitted Polynomial&#039;)&lt;br /&gt;
     xlabel(&#039;Temperature&#039;)&lt;br /&gt;
     ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
     legend()&lt;br /&gt;
     title(&#039;Degree of Fitted Polynomial = &#039;+str(degree))&lt;br /&gt;
     show()&lt;br /&gt;
&lt;br /&gt;
Some additional lines were added to the above code to label and mark the graphs with lines to show the position of the peak.    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116 2x2 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 16&#039;&#039;&#039; Heat Capacity vs Temperature for a 2x2 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH1164x4 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 17&#039;&#039;&#039; Heat Capacity vs Temperature for a 4x4 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 8x8 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 18&#039;&#039;&#039; Heat Capacity vs Temperature for a 8x8 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 16x16 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 19&#039;&#039;&#039; Heat Capacity vs Temperature for a 16x16 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
File:JGH116 32x32 peak fit.png|&amp;lt;center&amp;gt;&#039;&#039;&#039;Figure 20&#039;&#039;&#039; Heat Capacity vs Temperature for a 32x32 lattice.&amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The heat capacity was easier to fit for a smaller lattice. As the lattice grew larger, the peak became significantly noisier, and so it was difficult to fit a polynomial to the peak. For the 32x32 lattice, a polynomial was chosen that approximately lined up with the peak. A dashed line has been added to all the plots to show how the peak of the fit corresponds to the peak in the heat capacity.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt; TASK: Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two columns: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To extract the Curie temperature from the data, the temperature at which the heat capacity is at a maximum must be found. Therefore, the following piece of code was added to the fitting function above:&lt;br /&gt;
&lt;br /&gt;
 Cmax=np.max(fitted_C_values)&lt;br /&gt;
 print(T_range[fitted_C_values==Cmax])&lt;br /&gt;
&lt;br /&gt;
This finds the maximum value of the heat capacity in the fit, and prints the temperature at which it occurs. The calculated Curie temperatures for different lattice sizes are shown in the table below:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center; width: 200px; height: 200px;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
|&amp;lt;b&amp;gt;2&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;4&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;8&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;16&amp;lt;/b&amp;gt;||&amp;lt;b&amp;gt;32&amp;lt;/b&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! Curie Temperature &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;&lt;br /&gt;
|2.56798799||2.43732733||2.34666667||2.30819822||2.28549553&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From [15], if &amp;lt;math&amp;gt;1/L&amp;lt;/math&amp;gt; is plotted against &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt; there should be a straight line. By plotting this data and fitting the line as in figures 21 &amp;amp; 22, it is possible to find &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery class=&amp;quot;center&amp;quot; mode=&amp;quot;nolines&amp;quot; heights=&amp;quot;500px&amp;quot; widths=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
File:JGH116Curieplot.png|&#039;&#039;&#039;Figure 21&#039;&#039;&#039;&lt;br /&gt;
File:JGH116Invcurietemp.png|&#039;&#039;&#039;Figure 22&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From this fit, the Curie temperature for an infinite lattice is found to be:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_{C,\infty} = 2.27197698 \pm 0.49% &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where the error is calculated taken from the covariance matrix from the &amp;lt;code&amp;gt;numpy.polyfit()&amp;lt;/code&amp;gt; function. The analytical result found by Onsager in 1944 was:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; T_{C,\infty} = \frac{2}{\ln(1+\sqrt2)} = 2.26918531421 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The percentage difference between the calculated and literature value is ~0.12%. Despite this being within experimental error, it is always useful to discuss sources of error in the experiment. The main sources of error in calculating &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; are:&lt;br /&gt;
#Problems with fitting polynomials to the peak - for the higher lattice sizes, it is difficult to get an accurate polynomial to fit the peak due to the amount of noise present. This creates variation in &amp;lt;math&amp;gt;T_{C,L}&amp;lt;/math&amp;gt;, and therefore potential error in the value of &amp;lt;math&amp;gt;T_{C, \infty}&amp;lt;/math&amp;gt;.&lt;br /&gt;
#The problems with noise in the heat capacity comes from not running the simulation for long enough to obtain a good average. The program written in C++ was able to be run with much longer runtimes, reducing the number of fluctuations close to the peak in the heat capacity.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Invcurietemp.png&amp;diff=736772</id>
		<title>File:JGH116Invcurietemp.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:JGH116Invcurietemp.png&amp;diff=736772"/>
		<updated>2018-11-21T02:30:43Z</updated>

		<summary type="html">&lt;p&gt;Jgh116: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jgh116</name></author>
	</entry>
</feed>