<?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=Afg216</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=Afg216"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Afg216"/>
	<updated>2026-05-17T12:05:17Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737249</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737249"/>
		<updated>2018-11-24T12:52:30Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 12.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 13. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 13&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below in Figure 14.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 14&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269&amp;lt;ref name=&amp;quot;litval&amp;quot;&amp;gt;J. Kotze, &amp;quot;An Introduction to Monte Carlo Methods for an Ising Model of a Ferromagnet&amp;quot;, &#039;&#039;&#039;2008&#039;&#039;&#039;, &#039;&#039;22&#039;&#039;, https://arxiv.org/pdf/0803.0217.pdf&amp;lt;/ref&amp;gt;). The relative error is only 0.441 %. Given the many sources of error (including the error in polynomial fits and the high errors in the critical regions of the measurements) this seems a very reasonable result. The experiment would have been improved by taking more temperature points in the original runs to improve resolution, by taking measurements from more lattice sizes (as a fit of only three points is never ideal), by taking more repeats to lessen the impact of the error in the critical region (which was unfortunately not possible here due to time restraints), by improving the equilibration delay code (which was done visually with no real quantitative justification) or by using a more efficient processing language than python - the C++ data was much more extensive and proved the usefulness in using another language, particularly in the reduuction of run time, allowing for more repeats and smoother data to be acquired. In order to improve the equilibration delay code, there might have been a way to automate the delay to remove some of the qualitative error in looking for the point of equilibration. That also would have allowed for different delays to be used for the different lattice sizes which would again have improved the experiment. This might have been done by assessing the standard deviation of points within a range, and only taking the statistics data once it had fallen below a set value, representing the extent of fluctuations at equilibrium.&lt;br /&gt;
&lt;br /&gt;
=Conclusion=&lt;br /&gt;
The simulations successfully yielded results comparable to those in the literature; the experiment was very successful bearing in mind the scope for error involved and the lack of repitition within it. The results were found quickly and with relative ease and as such reflected the useful (and transferable) nature of the python programming language within scientific computation. In practice, a much broader range of values and a much greater number of runs should be carried out to improve the experiment such that it can be treated as reliable.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737248</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737248"/>
		<updated>2018-11-23T12:39:25Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 12.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 12&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 13. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 13&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below in Figure 14.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 14&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269&amp;lt;ref name=&amp;quot;litval&amp;quot;&amp;gt;J. Kotze, &amp;quot;An Introduction to Monte Carlo Methods for an Ising Model of a Ferromagnet&amp;quot;, &#039;&#039;&#039;2008&#039;&#039;&#039;, &#039;&#039;22&#039;&#039;, https://arxiv.org/pdf/0803.0217.pdf&amp;lt;/ref&amp;gt;). The relative error is only 0.441 %. Given the many sources of error (including the error in polynomial fits and the high errors in the critical regions of the measurements) this seems a very reasonable result. The experiment would have been improved by taking more temperature points in the original runs to improve resolution, by taking measurements from more lattice sizes (as a fit of only three points is never ideal), by taking more repeats to lessen the impact of the error in the critical region (which was unfortunately not possible here due to time restraints), by improving the equilibration delay code (which was done visually with no real quantitative justification) or by using a more efficient processing language than python - the C++ data was much more extensive and proved the usefulness in using another language, particularly in the reduuction of run time, allowing for more repeats and smoother data to be acquired. In order to improve the equilibration delay code, there might have been a way to automate the delay to remove some of the qualitative error in looking for the point of equilibration. That also would have allowed for different delays to be used for the different lattice sizes which would again have improved the experiment. This might have been done by assessing the standard deviation of points within a range, and only taking the statistics data once it had fallen below a set value, representing the extent of fluctuations at equilibrium.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737247</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737247"/>
		<updated>2018-11-23T10:14:37Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269&amp;lt;ref name=&amp;quot;litval&amp;quot;&amp;gt;J. Kotze, &amp;quot;An Introduction to Monte Carlo Methods for an Ising Model of a Ferromagnet&amp;quot;, &#039;&#039;&#039;2008&#039;&#039;&#039;, &#039;&#039;22&#039;&#039;, https://arxiv.org/pdf/0803.0217.pdf&amp;lt;/ref&amp;gt;). The relative error is only 0.441 %. Given the many sources of error (including the error in polynomial fits and the high errors in the critical regions of the measurements) this seems a very reasonable result. The experiment would have been improved by taking more temperature points in the original runs to improve resolution, by taking measurements from more lattice sizes (as a fit of only three points is never ideal), by taking more repeats to lessen the impact of the error in the critical region (which was unfortunately not possible here due to time restraints), by improving the equilibration delay code (which was done visually with no real quantitative justification) or by using a more efficient processing language than python - the C++ data was much more extensive and proved the usefulness in using another language, particularly in the reduuction of run time, allowing for more repeats and smoother data to be acquired. In order to improve the equilibration delay code, there might have been a way to automate the delay to remove some of the qualitative error in looking for the point of equilibration. That also would have allowed for different delays to be used for the different lattice sizes which would again have improved the experiment. This might have been done by assessing the standard deviation of points within a range, and only taking the statistics data once it had fallen below a set value, representing the extent of fluctuations at equilibrium.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737246</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737246"/>
		<updated>2018-11-23T10:13:17Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269&amp;lt;ref name=&amp;quot;litval&amp;quot;&amp;gt;J. Kotze, &amp;quot;An Introduction to Monte Carlo Methods for an Ising Model of a Ferromagnet&amp;quot;, &#039;&#039;&#039;2008&#039;&#039;&#039;, &#039;&#039;22&#039;&#039;, https://arxiv.org/pdf/0803.0217.pdf&amp;lt;/ref&amp;gt;). The relative error is only 0.441 %. Given the many sources of error (including the error in polynomial fits and the high errors in the critical regions of the measurements) this seems a very reasonable result. The experiment would have been improved by taking more temperature points in the original runs to improve resolution, by taking more repeats to lessen the impact of the error in the critical region (which was unfortunately not possible here due to time restraints), by improving the equilibration delay code (which was done visually with no real quantitative justification) or by using a more efficient processing language than python - the C++ data was much more extensive and proved the usefulness in using another language, particularly in the reduuction of run time, allowing for more repeats and smoother data to be acquired. In order to improve the equilibration delay code, there might have been a way to automate the delay to remove some of the qualitative error in looking for the point of equilibration. That also would have allowed for different delays to be used for the different lattice sizes which would again have improved the experiment. This might have been done by assessing the standard deviation of points within a range, and only taking the statistics data once it had fallen below a set value, representing the extent of fluctuations at equilibrium.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737245</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737245"/>
		<updated>2018-11-23T10:12:43Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269&amp;lt;ref name=&amp;quot;litval&amp;quot;&amp;gt;J. Kotze, &amp;quot;An Introduction to Monte Carlo Methods for an Ising Model of a Ferromagnet&amp;quot;, &#039;&#039;&#039;2008&#039;&#039;&#039;, &#039;&#039;22&#039;&#039;, {{https://arxiv.org/pdf/0803.0217.pdf}}&amp;lt;/ref&amp;gt;). The relative error is only 0.441 %. Given the many sources of error (including the error in polynomial fits and the high errors in the critical regions of the measurements) this seems a very reasonable result. The experiment would have been improved by taking more temperature points in the original runs to improve resolution, by taking more repeats to lessen the impact of the error in the critical region (which was unfortunately not possible here due to time restraints), by improving the equilibration delay code (which was done visually with no real quantitative justification) or by using a more efficient processing language than python - the C++ data was much more extensive and proved the usefulness in using another language, particularly in the reduuction of run time, allowing for more repeats and smoother data to be acquired. In order to improve the equilibration delay code, there might have been a way to automate the delay to remove some of the qualitative error in looking for the point of equilibration. That also would have allowed for different delays to be used for the different lattice sizes which would again have improved the experiment. This might have been done by assessing the standard deviation of points within a range, and only taking the statistics data once it had fallen below a set value, representing the extent of fluctuations at equilibrium.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737244</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737244"/>
		<updated>2018-11-23T10:01:59Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Introduction to the Ising model: Tasks 1, 2 and 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737243</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737243"/>
		<updated>2018-11-23T09:59:33Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!C&amp;lt;sub&amp;gt;Max&amp;lt;/sub&amp;gt;&lt;br /&gt;
!T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|0.4151056&lt;br /&gt;
|2.4958959&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|0.8083970 &lt;br /&gt;
|2.4654655&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|1.1525856 &lt;br /&gt;
|2.3687688&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|1.3887500 &lt;br /&gt;
|2.3073073&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|1.2552730&lt;br /&gt;
|2.3663664&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737242</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737242"/>
		<updated>2018-11-23T09:53:24Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits. Particularly for smaller lattice sizes the fits do seem overtly adequate, but the larger lattices (namely 16x16 and 32x32) still do not fit very well with the simulation data.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737241</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737241"/>
		<updated>2018-11-23T09:49:59Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here with the 4x4 size used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737240</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737240"/>
		<updated>2018-11-23T09:48:32Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below with the 4x4 used as an example.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_32x32_afg216.png&amp;diff=737239</id>
		<title>File:Heatcapvstempc++ 32x32 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_32x32_afg216.png&amp;diff=737239"/>
		<updated>2018-11-23T09:46:49Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_16x16_afg216.png&amp;diff=737238</id>
		<title>File:Heatcapvstempc++ 16x16 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_16x16_afg216.png&amp;diff=737238"/>
		<updated>2018-11-23T09:44:48Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_8x8_afg216.png&amp;diff=737237</id>
		<title>File:Heatcapvstempc++ 8x8 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_8x8_afg216.png&amp;diff=737237"/>
		<updated>2018-11-23T09:44:33Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_2x2_afg216.png&amp;diff=737236</id>
		<title>File:Heatcapvstempc++ 2x2 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempc%2B%2B_2x2_afg216.png&amp;diff=737236"/>
		<updated>2018-11-23T09:44:22Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737235</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737235"/>
		<updated>2018-11-23T09:44:06Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_2x2_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 2x2 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_8x8_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 8x8 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_16x16_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Heat capacity of a 16x16 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_32x32_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 11&#039;&#039;&#039;: Heat capacity of a 32x32 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737234</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737234"/>
		<updated>2018-11-23T09:41:16Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the &#039;CMP Modelling.ipynb&#039; notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)                                                                  #Loading the C++ data.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)    #Plotting the python data.&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)                       #Plotting the C++ data.&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)                                                                        #Adding a legend.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points for the 4x4 lattice is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]                                                                 #Generating the temperature range from the averaged data &#039;fourav&#039;.&lt;br /&gt;
C = heatcapacity(fourav, 4)                                                     #Generating the heat capacity data from &#039;fourav&#039;.&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)                                                #Fitting with a 15 order polynomial.&lt;br /&gt;
T_min = np.min(T)                                                               #Setting the range of the fit points as the full range of the data.&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)                                   #Plotting the python data.&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)                        #Plotting the polynomial fit.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook &#039;CMP Modelling.ipynb&#039; attached. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9. Note that due to high uncertainty in the critical region around the Curie temperature we cannot have great confidence in the fits.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737233</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737233"/>
		<updated>2018-11-23T09:33:58Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code. All of the lattice sizes matched the C++ data fairly well (and can be seen in the CMP Modelling notebook attached). The 32x32 lattice size matched the least well, likely due to the larger uncertainty associated with the region around the peak; it fit better with the averaged data rather than with any individual run, proving the usefulness of repeats.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook CMP modelling in the file submitted. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737232</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737232"/>
		<updated>2018-11-22T22:52:33Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
The rest of the fits are shown in the notebook CMP modelling in the file submitted. In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737231</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737231"/>
		<updated>2018-11-22T22:39:36Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit errors. not enough points or repeats or steps , python and time limited (wow C++ was much better).&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737230</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737230"/>
		<updated>2018-11-22T22:25:19Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
This compares favourably with the literature value of 2.269.&lt;br /&gt;
&lt;br /&gt;
Sources of error. equilibration delay. fit. not enough points or repeats python and time limited.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737229</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737229"/>
		<updated>2018-11-22T22:18:04Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice, as demonstrated by the scaling relation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept, &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;, is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Ctempvslatticesize_afg216.png&amp;diff=737228</id>
		<title>File:Ctempvslatticesize afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Ctempvslatticesize_afg216.png&amp;diff=737228"/>
		<updated>2018-11-22T22:14:39Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737227</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737227"/>
		<updated>2018-11-22T22:14:18Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the graph from which the Curie temperature of a theoretical infinite lattice could be extrapolated is shown below. In fitting, the first and last points (from the 2x2 and 32x32 lattices) were left out as both seemed anomalous.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Tfit = np.polyfit(invCTlatticevals[1:4], invCTtempvals[1:4], 1)&lt;br /&gt;
Lrange = np.linspace(0, 0.5, 1000)&lt;br /&gt;
&lt;br /&gt;
fitted_T_values = np.polyval(fit, T_range)&lt;br /&gt;
xlabel(&#039;1 / Lattice Dimension&#039;)&lt;br /&gt;
ylabel(&#039;Curie Temperature Estimate / K&#039;)&lt;br /&gt;
title(&#039;Curie Temperature Vs. the Reciprocal of Lattice Size with a Linear Fit&#039;)&lt;br /&gt;
plot((invCTlatticevals), invCTtempvals, marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(Lrange, (Tfit[0]*Lrange + Tfit[1]), marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
print(Tfit[1])&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The y intercept on the graph corresponds to the value of the Curie temperature of an infinitely large lattice. This is given by the print command at the end of the above code. The extrapolated value was 2.259. The graph is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:ctempvslatticesize_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 10&#039;&#039;&#039;: Curie temperature against reciprocal of lattice size with a linear fit.]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempresfitted_4x4_afg216.png&amp;diff=737226</id>
		<title>File:Heatcapvstempresfitted 4x4 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempresfitted_4x4_afg216.png&amp;diff=737226"/>
		<updated>2018-11-22T21:10:00Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737225</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737225"/>
		<updated>2018-11-22T21:09:30Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
In general, higher order polynomials garnered a better fit for all lattice sizes. The fits for the 32x32 lattice and 16x16 lattice are much worse, even at higher orders, but they were improved in the next task.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The modified code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&lt;br /&gt;
T_min = 1                                           #These set the minimum and maximum values of the range for the fit.&lt;br /&gt;
T_max = 4&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;
C4max = np.max(fitted_C_values)&lt;br /&gt;
T4max = T_range[fitted_C_values == C4max]           #This code retrieves the maximum value of C and the corresponding value of T.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fit within the restricted range is shown below in Figure 9.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempresfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 9&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a restricted range polynomial fit to the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempfitted_4x4_afg216.png&amp;diff=737224</id>
		<title>File:Heatcapvstempfitted 4x4 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstempfitted_4x4_afg216.png&amp;diff=737224"/>
		<updated>2018-11-22T20:56:46Z</updated>

		<summary type="html">&lt;p&gt;Afg216: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737223</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737223"/>
		<updated>2018-11-22T20:55:51Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
The code used to plot the C vs T points and a polynomial fit to the points is shown below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T = fourav[:,0]&lt;br /&gt;
C = heatcapacity(fourav, 4)&lt;br /&gt;
fit = np.polyfit(T, C*(4*4), 15)&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;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;Heat Capacity Against Temperature and Polynomial Fit for a 4x4 Lattice&#039;)&lt;br /&gt;
plot(T,C*(4*4), marker=&#039;.&#039;, linestyle=&#039;None&#039;)&lt;br /&gt;
plot(T_range, fitted_C_values, marker=&#039;&#039;, linestyle=&#039;-&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The requisite graph for the 4x4 lattice is shown below in Figure 8.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempfitted_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 8&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and a polynomial fit of the points of order 15.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737222</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737222"/>
		<updated>2018-11-22T20:47:21Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure 7. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737221</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737221"/>
		<updated>2018-11-22T20:46:17Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 7&#039;&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737220</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737220"/>
		<updated>2018-11-22T20:45:58Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Locating the Curie Temperature: Tasks 17, 18, 19 and 20 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|&#039;&#039;Figure 7&#039;&#039;: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737219</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737219"/>
		<updated>2018-11-22T20:45:05Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Determining the Heat Capacity: Tasks 15 and 16 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_16x16_afg216.png&amp;diff=737218</id>
		<title>File:Heatcapvstemp 16x16 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_16x16_afg216.png&amp;diff=737218"/>
		<updated>2018-11-22T20:41:03Z</updated>

		<summary type="html">&lt;p&gt;Afg216: Afg216 uploaded a new version of File:Heatcapvstemp 16x16 afg216.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_32x32_afg216.png&amp;diff=737217</id>
		<title>File:Heatcapvstemp 32x32 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_32x32_afg216.png&amp;diff=737217"/>
		<updated>2018-11-22T20:40:48Z</updated>

		<summary type="html">&lt;p&gt;Afg216: Afg216 uploaded a new version of File:Heatcapvstemp 32x32 afg216.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_8x8_afg216.png&amp;diff=737216</id>
		<title>File:Heatcapvstemp 8x8 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_8x8_afg216.png&amp;diff=737216"/>
		<updated>2018-11-22T20:39:34Z</updated>

		<summary type="html">&lt;p&gt;Afg216: Afg216 uploaded a new version of File:Heatcapvstemp 8x8 afg216.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_4x4_afg216.png&amp;diff=737215</id>
		<title>File:Heatcapvstemp 4x4 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_4x4_afg216.png&amp;diff=737215"/>
		<updated>2018-11-22T20:39:15Z</updated>

		<summary type="html">&lt;p&gt;Afg216: Afg216 uploaded a new version of File:Heatcapvstemp 4x4 afg216.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_2x2_afg216.png&amp;diff=737214</id>
		<title>File:Heatcapvstemp 2x2 afg216.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Heatcapvstemp_2x2_afg216.png&amp;diff=737214"/>
		<updated>2018-11-22T20:38:51Z</updated>

		<summary type="html">&lt;p&gt;Afg216: Afg216 uploaded a new version of File:Heatcapvstemp 2x2 afg216.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737213</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737213"/>
		<updated>2018-11-22T20:36:52Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of System Size: Task 14 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;Task 14: 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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737212</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737212"/>
		<updated>2018-11-22T20:34:50Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of System Size: Task 14 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The same simulation as before (0.2 to 5 K in steps of 0.1, 10000 equilibration delay and 100000 total cycles) was carried out for 2x2, 4x4, 16x16 and 32x32 element lattices. Only three repeats of each lattice size were carried out due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed.&lt;br /&gt;
&lt;br /&gt;
A sample of the code used to plot the required graphs is shown below. As before, there is a normalisation factor missing in the code and as such the values should be divided by their number of elements (i.e. 2x2 by 4, 4x4 by 16 etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
four1 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
four2 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
four3 = loadtxt(&amp;quot;4x4_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(four1))):&lt;br /&gt;
    val0=[avgEs(four1)[i],avgEs(four2)[i],avgEs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(four1)+avgEs(four2)+avgEs(four3))/3&lt;br /&gt;
temps = temprange(four1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(four1))):&lt;br /&gt;
    val0=[avgMs(four1)[i],avgMs(four2)[i],avgMs(four3)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageMagnetisations = (avgMs(four1)+avgMs(four2)+avgMs(four3))/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Magnetisation per Spin of a 4x4 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The graphs reflecting the effect of lattice size are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737211</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737211"/>
		<updated>2018-11-22T20:28:07Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is near 1 at low temperatures (below the Curie temperature) but decreases dramatically above &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; to settle around zero as the system is no longer spontaneously magnetised, as discussed earlier. Note that the graphs have not been normalised to lattice size due to an error in the code - the 8x8 lattice graph here shows values 64 times larger than they should be.&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737210</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737210"/>
		<updated>2018-11-22T20:20:57Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
eight1 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)          #Loading the relevant simulation files&lt;br /&gt;
eight2 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
eight3 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
eight4 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
eight5 = loadtxt(&amp;quot;8x8_temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):                                                                #Defining functions to extract the required data from the files.&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []                                                                          #Generating a list of standard deviation values.&lt;br /&gt;
for i in range(0,len(avgEs(eight1))):&lt;br /&gt;
    val0=[avgEs(eight1)[i],avgEs(eight2)[i],avgEs(eight3)[i],avgEs(eight4)[i],avgEs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(eight1)+avgEs(eight2)+avgEs(eight3)+avgEs(eight4)+avgEs(eight5))/5&lt;br /&gt;
temps = temprange(eight1)                                                             #Creating a list of average energies from the repeats run.&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)              #Plotting the required graph with error bars generated from the repeat runs.&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(eight1))):&lt;br /&gt;
    val0=[avgMs(eight1)[i],avgMs(eight2)[i],avgMs(eight3)[i],avgMs(eight4)[i],avgMs(eight5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval                                                         #Generating the equivalent standard deviation list but for magnetisation.&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(eight1)+avgMs(eight2)+avgMs(eight3)+avgMs(eight4)+avgMs(eight5))/5&lt;br /&gt;
                                                                                      #Creating an equivalent average value list for magnetisation.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)        #Plotting the equivalent graph for magnetisation.&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that the energy per spin in the system increases with temperature. The standard deviation is much higher in the transition region (between entropic and energetically controlled equilibria) anchored around the Curie temperature. The magnetisation per spin is&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737209</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737209"/>
		<updated>2018-11-22T18:47:48Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6. They were generated by performing 100,000 Monte Carlo cycles on an 8x8 Ising Lattice at temperature intervals of 0.1 K from 0.2 K to 5 K.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
It can be easily seen that&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737208</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737208"/>
		<updated>2018-11-22T18:41:21Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general (as the flipping of one spin has less of an impact on the whole systems when there are more spin elements in the system), although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737207</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737207"/>
		<updated>2018-11-22T18:40:11Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
From here on an equilibration delay is taken to be 10,000, as for the relevant lattice sizes and temperatures investigated this accounts for equilibration. The downsides to this assumption are discussed later.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (of energy per spin against temperature for an 8x8 Ising Lattice) is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generated graphs are shown below in Figures 5 and 6.&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure 5&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 6&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737206</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737206"/>
		<updated>2018-11-22T18:34:53Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation of equilibration time with lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737205</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737205"/>
		<updated>2018-11-22T18:29:00Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. Helmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Below in Figures 3 and 4 the ILanim.py results are shown. Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 3&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 4&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737204</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737204"/>
		<updated>2018-11-22T18:24:53Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* CMP Modelling Computational Laboratory */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds &amp;lt;ref name=&amp;quot;physuni&amp;quot;&amp;gt;Physicsoftheuniverse.com, &amp;quot;The Universe by Numbers&amp;quot;, &#039;&#039;https://www.physicsoftheuniverse.com/numbers.html&#039;&#039;, accessed 15/11/2018&amp;lt;/ref&amp;gt;. This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. HElmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737203</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737203"/>
		<updated>2018-11-22T18:21:26Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* The Effect of Temperature: Tasks 12 and 13 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds REFERENCE (https://www.physicsoftheuniverse.com/numbers.html). This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. HElmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;Task 12: The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13: Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737202</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737202"/>
		<updated>2018-11-22T18:20:47Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Calculating the energy and magnetisation: Tasks 4 and 5 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin element, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in Figure 2. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure 2&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds REFERENCE (https://www.physicsoftheuniverse.com/numbers.html). This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. HElmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737201</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737201"/>
		<updated>2018-11-22T18:16:56Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Introduction to the Ising model: Tasks 1, 2 and 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt; as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions - a net interaction energy change of &amp;lt;math&amp;gt;12J&amp;lt;/math&amp;gt;. Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin elemen, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in FIGURE X. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds REFERENCE (https://www.physicsoftheuniverse.com/numbers.html). This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. HElmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737200</id>
		<title>Rep:Mod:Afg216CMP</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:Afg216CMP&amp;diff=737200"/>
		<updated>2018-11-22T18:15:28Z</updated>

		<summary type="html">&lt;p&gt;Afg216: /* Introduction: The Ising Model, Curie Temperature, Partition Functions, Monte Carlo Method, Background and Aims (REFERENCES) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=CMP Modelling Computational Laboratory=&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
In this experiment, the Monte Carlo algorithm and the Ising Model of ferromagnetic materials are used to investigate energies and magnetisations of a two-dimensional ferromagnetic lattice. The model is used to predict the heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, and Curie temperature, &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt;, of the system. The Ising Model treats a ferromagnetic material as a simple lattice of magnetic spins, &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt;, which can be either up or down - &amp;lt;math&amp;gt; s_i = \pm 1&amp;lt;/math&amp;gt;; the lattice energy derives simply from the interactions of directly neighbouring spins and the lattice is treated as periodic - it repeats identically in all dimensions&amp;lt;ref name=&amp;quot;ja9825332&amp;quot;&amp;gt;F. Bresme, O. Robotham, &amp;quot;Third Year CMP Compulsory Experiment Lab Script&amp;quot;, &#039;&#039;https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment&#039;&#039;, accessed 20/11/2018&amp;lt;/ref&amp;gt;. Here a lattice in two dimensions only is used for simplicity of computation.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm (voted the Top Algorithm of the 20th Century &amp;lt;ref name=&amp;quot;algorithm2&amp;quot;&amp;gt;J. Dongarra , F. Sullivan, &amp;quot;Guest Editors Introduction to the Top 10 Algorithms&amp;quot;, &#039;&#039;Computing in Sci. and Eng.&#039;&#039;, &#039;&#039;&#039;2000&#039;&#039;&#039;, &#039;&#039;2&#039;&#039;, 22-23.{{DOI|10.1109/MCISE.2000.814652}}&amp;lt;/ref&amp;gt;) is used to significantly reduce the computational requirements of the situation such that it becomes reasonable to carry on a desktop computer. It does this by restricting the model to take only spin configurations which have above a certain threshold probability of existence, defined by the Boltzmann distribution (which uses the temperature at which the simulation is being run).&lt;br /&gt;
&lt;br /&gt;
The Ising Model allows for the prediction and observation of the phase change that occurs at the Curie temperature, when it is used in two or more dimensions. The Curie temperature marks the point at which the competing energetic and entropic attributes of the system balance - just above absolute zero a system of magnetic spins will be aligned with all spins parallel (all with the same value of either &amp;lt;math&amp;gt;s_i = 1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;s_i = -1&amp;lt;/math&amp;gt;) as that is the lowest possible energy configuration. Above the Curie temperature, the system has enough thermal energy to overcome this energetic barrier and reorganise to maximise the entropy andd gain the energetic benefits associated with high entropy&amp;lt;ref name=&amp;quot;atkins&amp;quot;&amp;gt;P. Atkins, J. de Paula, &amp;quot;Atkins&#039; Physical Chemistry&amp;quot;, ISBN : 978-0-19-969740-3&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A range of lattice sizes and temperatures are tested and the magnetisations and energies associated with each investigated. From these simulations, heat specific capacities were extracted using the energies&#039; variances and by extension the Curie temperatures of the system were approximated. From these values the Curie temperature of a real ferromagnetic material can be estimated and is done so, by extrapolating to an infinitely large Ising Lattice, which is a reasonable approximation.&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising model: Tasks 1, 2 and 3==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lowest energy configuration of the Ising model has all spins parallel (all &amp;lt;math&amp;gt;s_i&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_j&amp;lt;/math&amp;gt; with value 1 or -1). When this is the case, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
becomes equal to the number of neighbours of each spin unit, as &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; becomes 1. Each spin unit in a &amp;lt;math&amp;gt;D -&amp;lt;/math&amp;gt; dimensional lattice has &amp;lt;math&amp;gt;2 D&amp;lt;/math&amp;gt; immediately adjacent neighbours and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It follows that as&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\sum_i^N = N&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
then, as the total expression for the energy is REFERENCE:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
the energy in this minimum energy configuration can be expressed as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \times N \times 2 D&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(where the half prevents double counting of interactions) and thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - D N J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required. The multiplicity of this system is defined as the number of different ways of arranging the unit spins. As the spins are indistinguishable and all spins in this particular case are equal (at either 1 or -1) there are only two ways of arranging the system (where all spins are parallel or antiparallel) and as such the multiplicity, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; , is equal to 2. The entropy, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, of the system is given by the formula &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln \Omega&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;k_B = 1.38064852 \times 10^{-23}  m^2 kg  s^{-2}  K^{-1}&amp;lt;/math&amp;gt;, Boltzmann&#039;s Constant.&lt;br /&gt;
&lt;br /&gt;
So, the entropy of this system where &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S = k_B \ln 2&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2: 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;
The energy of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E = - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus the energy difference between a system with all spins at 1 or -1 and a system with all but one spin at 1 or -1 and the other of the opposite spin to the rest is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j) - (- \frac{1}{2} J N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and this difference in interaction is 12 as, in three dimensions, each spin has 6 immediately adjacent neighbours. When one spin is flipped, six favourable parallel interactions are replaced by six unfavourable antiparallel interactions, giving a total interaction difference of 12. WRONG Therefore:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta E = 12 J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The entropy change is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln \Omega_{final} - k_B \ln \Omega_{initial} = k_B (\ln (2 {1000 \choose 1}) - \ln 2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\Delta S = k_B \ln 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3: 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;
The magnetisation, &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt;, of the system is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \sum_i s_i&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&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 respective magnetisations of the &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;D = 2&amp;lt;/math&amp;gt; lattices shown in Figure 1 are consequently as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 1} = \sum_i s_i = (3) \times (1) + (2) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M_{D = 2} = \sum_i s_i = (4+3+3+2+1) \times (1) + (1+2+2+3+4) \times (-1) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At absolute zero, you would expect the Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; to have magnetisation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M = \pm 1000&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
depending on the direction that all spins in the lattice take - they should all be parallel at absolute zero as they do not have the thermal energy available to them that is required to overcome the energetic barrier associated with flipping spins.&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation: Tasks 4 and 5==&lt;br /&gt;
&#039;&#039;&#039;Task 4: Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Note: as suggested in the laboratory script, &amp;lt;math&amp;gt; J = 1.0&amp;lt;/math&amp;gt; is assumed from here onwards as reduced units (in which &amp;lt;math&amp;gt;J = k_B&amp;lt;/math&amp;gt;) are used.&lt;br /&gt;
&lt;br /&gt;
The python script used to define the Ising Lattice object used in the experiment along with the first functions used to find the energy and magnetisation of the lattice are shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&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;
&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;
        J=1.0&lt;br /&gt;
        enesum=0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #Here two loops are used to loop across every spin element in both dimensions.&lt;br /&gt;
                enesum=enesum+(self.lattice[i,j]*(self.lattice[i,(j-1)]+self.lattice[(i-1),j]))       #Here a loop is used to sum the vertical and horizontal interactions calculated for each spin elemen, with &#039;-1&#039; used to account for the periodic nature of the lattice&lt;br /&gt;
        energy = -1*J*enesum                                                                          #The sum of interactions is converted to a real energy value - 0.5 is not needed as the interactions are not double counted to reduce computational demand.&lt;br /&gt;
        return energy&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 range(0,self.n_rows):&lt;br /&gt;
            for j in range(0,len(self.lattice[i])):                                                   #The values of all spin elements are simply summed by looping across the rows and columns.&lt;br /&gt;
                magnetisation=magnetisation+self.lattice[i,j]&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The results of the ILcheck.py script can be seen below in FIGURE X. It shows that the energy and magnetisation functions shown above are functioning correctly by showing a maximum energy, minimum energy and random configuration of the lattice spins.&lt;br /&gt;
&lt;br /&gt;
[[File:ILcheck_image_afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: ILcheck.py results]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation: Tasks 6, 7 and 8==&lt;br /&gt;
&#039;&#039;&#039;Task 6: 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;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each spin element can take two possible values (&amp;lt;math&amp;gt;s_i = \pm 1&amp;lt;/math&amp;gt;) and thus the total number of spin configurations for a 10 by 10 element lattice is &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; (as there are 100 spin elements). To calculate the expected or average magnetisation at a certain temperature, &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;, all of these configurations must be considered. Consequently, it would take:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; configurations &amp;lt;math&amp;gt;\div 1 \times 10^{9}&amp;lt;/math&amp;gt; configurations per second &amp;lt;math&amp;gt;= 1.27 \times 10^{21}&amp;lt;/math&amp;gt;seconds&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to run through all configurations. This is obviously ludicrous given that the age of the universe is estimated to be &amp;lt;math&amp;gt;4.32 \times 10^{17}&amp;lt;/math&amp;gt; seconds REFERENCE (https://www.physicsoftheuniverse.com/numbers.html). This shows that the computational method must be improved - this is done by using the Monte Carlo algorithm, as discussed in the introduction.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7: Implement a single cycle of the above 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;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Below the montecarlostep(T) and statistics() functions added to the IsingLattice object definition are shown. The algorithm functions by taking the starting spin configuration (defined by the __init__() function within the object), randomly flipping one spin and testing the configuration produced. The algorithm only accepts lattice configurations with energies lower than that which came before or with high enough probability of occurance when compared to the Boltzmann distribution - as the Boltzmann distribution is a function of temperature, which lattices would be accepted also depends on temperature. This generates a Boltzmann distributed set of lattice configurations from which the average energy and magnetisation can be calculated, and eliminates the need to consider every low probability configuration - which have negligible impact on the properties to be calculated - which in turn vastly reduces the computational demand of the experiment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.                                                                                     #The previous code within the IsingLattice object is as before.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        E0 = self.energy()&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]          #This code chooses a random spin element in the lattice and flips its value.&lt;br /&gt;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):                                         #This code chooses only high enough probability lattice configurations.&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]  #This code restores the configuration if the new configuration was too unlikely. &lt;br /&gt;
        self.n_cycles = self.n_cycles + 1&lt;br /&gt;
        self.E = self.E + self.energy()&lt;br /&gt;
        self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
        self.M = self.M + self.magnetisation()&lt;br /&gt;
        self.M2 = self.M2 + (self.magnetisation())**2                                 #This code updates the energy and magnetisation attributes of the lattice object after each step.&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):                                                             #This statistics() function calculates and returns the requested quantities at the end of each run.&lt;br /&gt;
        AvgE = self.E/self.n_cycles&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles)**2)&lt;br /&gt;
        AvgM = self.M/self.n_cycles&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Theoretically, spontaneous magnetisation is indeed expected below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the energetic cost of flipping the spins to maximise the system entropy is too great compared to the amount of thermal energy the system has - the system will align the spins and as such show a magnetisation, &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt;, of greater or less than zero. Quantitatively, this can be explained using Helmholtz Free Energy, &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt;, and the fact that the system always looks to minimise it. HElmholtz Free Energy is given by:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;A = U - T S&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and thus when &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; is low, the entropy has a much lower impact on &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; than &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;, the internal energy. This can be used to quantitatively find the tipping point &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; above which the system adjusts to maximise entropy.&lt;br /&gt;
&lt;br /&gt;
Note - ILanim.py had to be run on a different computer due to technical difficulties, hence the lines within the code screenshot indicating that it has been run by someone else. It can be seen that a minimum energy has been reached at this temperature (which must be below &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; as the system has reached equilibrium (all spins in the lattice have aligned and are parallel); a maximum magnetisation has also been reached for the same reason.&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_Screenshotafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of equilibrated 8x8 lattice at 1 K]]&lt;br /&gt;
&lt;br /&gt;
[[File:Task_8_codeafg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Screenshot of statistics generated by ILanim.py]]&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code: Tasks 9, 10 and 11==&lt;br /&gt;
&#039;&#039;&#039;Task 9: 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!&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
10 Runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;%run ILtimetrial&lt;br /&gt;
Took 6.491240794751832s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.198033647801431s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.39347229230993s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.2046913622484325s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.873771136789344s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.258122856385299s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.286337743869581s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.719355183591773s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.612273236569536s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial&lt;br /&gt;
Took 6.688410581865767s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|6.47&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.229&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This time trial data shows the inefficiencies present in that particular iteration of the IsingLattice object code; it is always desirable to run simulations as quickly as possible and improvements were then made.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10: 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!).&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The efficiency of the energy() and magnetisation() functions could be improved significantly; the resulting code is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;class IsingLattice:&lt;br /&gt;
.&lt;br /&gt;
.&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;
        J=1&lt;br /&gt;
        up = np.roll(self.lattice, 1, axis=0)&lt;br /&gt;
        side = np.roll(self.lattice, 1, axis=1)                               #This code duplicates the spin lattice and moves it up and right respectively.&lt;br /&gt;
        upE = np.multiply(up, self.lattice)&lt;br /&gt;
        sideE = np.multiply(side, self.lattice)                               #This code multiplies the original lattice with the &#039;up&#039; and &#039;side&#039; lattices respectively.&lt;br /&gt;
        totalE = -J*(upE + sideE)                                             #This code sums the interaction lattices and multiplies the summed lattice by J to give the real energy.&lt;br /&gt;
        return np.sum(totalE)&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 np.sum(self.lattice)                                           #This code sums all elements in the lattice succintly to give the overall magnetisation.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11: 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!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
10 further runs of the ILtimetrial.py script were carried out to account for fluctuations in performance due to differing background operations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; %run ILtimetrial.py&lt;br /&gt;
Took 0.36230830418159893s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3577631995347126s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3494842495103363s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3503130425857659s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35432486293695487s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3491284415440008s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3588639804305611s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.3561783145308208s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.36012299323451735s&lt;br /&gt;
&lt;br /&gt;
%run ILtimetrial.py&lt;br /&gt;
Took 0.35134796479554s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Quantity&lt;br /&gt;
!Value&lt;br /&gt;
|-&lt;br /&gt;
|Mean Time / s&lt;br /&gt;
|0.355&lt;br /&gt;
|-&lt;br /&gt;
|Standard Deviation &lt;br /&gt;
|0.00452&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The obvious significant reduction in average processing time (by 18.2 times) shows the dramatic increase in computational efficiency facilitated by the code change above.&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature: Tasks 12 and 13==&lt;br /&gt;
&#039;&#039;&#039;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the &#039;&#039;final&#039;&#039; 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in lattice size can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|2x2&lt;br /&gt;
|100&lt;br /&gt;
|[[File:1K_2x2_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|4x4&lt;br /&gt;
|200&lt;br /&gt;
|[[File:1K_4x4_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|8x8&lt;br /&gt;
|1000&lt;br /&gt;
|[[File:1K_8x8_100000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|32x32&lt;br /&gt;
|80000&lt;br /&gt;
|[[File:1K_32x32_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The simulations run to investigate the variation in equilibration time with temperature can be seen below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Temperature / K&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Approximate no. Cycles to Equilibration&lt;br /&gt;
!.png Graphic&lt;br /&gt;
|-&lt;br /&gt;
|0.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:0.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|16x16&lt;br /&gt;
|8000&lt;br /&gt;
|[[File:1K_16x16_150000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|1.5&lt;br /&gt;
|16x16&lt;br /&gt;
|10000&lt;br /&gt;
|[[File:1.5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|2.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:2K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|3.0&lt;br /&gt;
|16x16&lt;br /&gt;
|20000&lt;br /&gt;
|[[File:3K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4.0&lt;br /&gt;
|16x16&lt;br /&gt;
|5000&lt;br /&gt;
|[[File:4K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|5.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:5K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|10.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:10K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|15.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:15K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|20.0&lt;br /&gt;
|16x16&lt;br /&gt;
|0&lt;br /&gt;
|[[File:20K_16x16_500000stepsafg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this data it is easier to observe that at some point between 2 K and 3 K the Curie temperature is surpassed - at 3 K the system is high in entropy and lower in internal energy but at 2 K the entropy is minimised and the internal energy is maximised by aligning spins. At 3 K and above the magnetisation fluctuates around an equilibrium value of 0 but below it fluctuates around equilibrium non-zero values. It can also be seen that at higher temperatures more &#039;noise&#039; due to thermal fluctuations is seen and that larger lattices appear to take longer to equilibrate in general, although at higher temperatures this effect is reduced as the lattices begin approximately in equilibrium (as the random starting configuration is more likely to be around equilibrium at higher temperatures).&lt;br /&gt;
&lt;br /&gt;
The modified code which accounts for the delay in equilibration is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Performs 1 Monte Carlo step on the given lattice and updates the attributes of the lattice accordingly.&amp;quot;&lt;br /&gt;
        E0 = self.energy()&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;
        E1 = self.energy()&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        dE = E1 - E0 &lt;br /&gt;
        if dE &amp;gt; 0: &lt;br /&gt;
            if random_number &amp;gt; np.exp(-dE/T):&lt;br /&gt;
                self.lattice[random_i,random_j] = -1*self.lattice[random_i,random_j]&lt;br /&gt;
        self.n_cycles = self.n_cycles + 1                                               #Up to here, the code is the same as before.&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay cycle number is defined here.&lt;br /&gt;
        if self.n_cycles &amp;gt; equilibrationdelay:                                          #The code from here stops the statistics being recorded until the equilibration delay is passed.&lt;br /&gt;
            self.E = self.E + self.energy()&lt;br /&gt;
            self.E2 = self.E2 + (self.energy())**2&lt;br /&gt;
            self.M = self.M + self.magnetisation()&lt;br /&gt;
            self.M2 = self.M2 + (self.magnetisation())**2&lt;br /&gt;
        return(self.energy(), self.magnetisation())&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        &amp;quot;Returns the statistics associated with the Monte Carlo steps performed.&amp;quot;&lt;br /&gt;
        equilibrationdelay = N                                                          #The equilibration delay is also defined here.&lt;br /&gt;
        AvgE = self.E/(self.n_cycles-equilibrationdelay)                                #The adjustment for the delay in the statistics is here.&lt;br /&gt;
        AvgE2 = self.E2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        AvgM = self.M/(self.n_cycles-equilibrationdelay)&lt;br /&gt;
        AvgM2 = self.M2/((self.n_cycles-equilibrationdelay)**2)&lt;br /&gt;
        return (AvgE, AvgE2, AvgM, AvgM2, self.n_cycles)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, &#039;&#039;with error bars&#039;&#039;, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; 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 &amp;amp;mdash; you will need it later. Save the file as &#039;&#039;8x8.dat&#039;&#039; so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The code used to plot the required graph (FIGURE X).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rpt1 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000.dat&amp;quot;)&lt;br /&gt;
rpt2 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_2.dat&amp;quot;)&lt;br /&gt;
rpt3 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_3.dat&amp;quot;)&lt;br /&gt;
rpt4 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_4.dat&amp;quot;)&lt;br /&gt;
rpt5 = loadtxt(&amp;quot;temprange_0.2to5_0.1int_100000steps_delay10000_5.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def temprange(file):&lt;br /&gt;
    &#039;Retrieves temperature range from given file.&#039;&lt;br /&gt;
    temps = file[:,0]&lt;br /&gt;
    return temps&lt;br /&gt;
&lt;br /&gt;
def avgEs(file):&lt;br /&gt;
    &#039;Returns average energies at each temp from given file.&#039;&lt;br /&gt;
    avges = file[:,1]&lt;br /&gt;
    return avges&lt;br /&gt;
&lt;br /&gt;
def avgE2s(file):&lt;br /&gt;
    &#039;Returns average energies squared at each temp from given file.&#039;&lt;br /&gt;
    avge2s = file[:,2]&lt;br /&gt;
    return avge2s&lt;br /&gt;
&lt;br /&gt;
def avgMs(file):&lt;br /&gt;
    &#039;Returns average magnetisations at each temp from given file.&#039;&lt;br /&gt;
    avgMs = file[:,3]&lt;br /&gt;
    return avgMs&lt;br /&gt;
&lt;br /&gt;
def avgM2s(file):&lt;br /&gt;
    &#039;Returns average magnetisations squared at each temp from given file.&#039;&lt;br /&gt;
    avgM2s = file[:,4]&lt;br /&gt;
    return avgM2s&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgEs(rpt1))):&lt;br /&gt;
    val0=[avgEs(rpt1)[i],avgEs(rpt2)[i],avgEs(rpt3)[i],avgEs(rpt4)[i],avgEs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
&lt;br /&gt;
AverageEnergies = (avgEs(rpt1)+avgEs(rpt2)+avgEs(rpt3)+avgEs(rpt4)+avgEs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Energy per Spin / J kB&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageEnergies,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&lt;br /&gt;
stdvals = []&lt;br /&gt;
for i in range(0,len(avgMs(rpt1))):&lt;br /&gt;
    val0=[avgMs(rpt1)[i],avgMs(rpt2)[i],avgMs(rpt3)[i],avgMs(rpt4)[i],avgMs(rpt5)[i]]&lt;br /&gt;
    stddevval=[np.std(val0)]&lt;br /&gt;
    stdvals=stdvals+stddevval&lt;br /&gt;
    &lt;br /&gt;
AverageMagnetisations = (avgMs(rpt1)+avgMs(rpt2)+avgMs(rpt3)+avgMs(rpt4)+avgMs(rpt5))/5&lt;br /&gt;
temps = temprange(rpt1)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Average Magnetisation per Spin&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Magnetisation and Energy per Spin of an 8x8 Lattice with Error Bars&#039;)&lt;br /&gt;
errorbar(temps,AverageMagnetisations,stdvals,None,linestyle=&#039;none&#039;,marker=&#039;.&#039;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Energy per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center|&#039;&#039;&#039;Figure X&#039;&#039;&#039;: Magnetisation per Spin against Temperature with Error Bars.]]&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size: Task 14==&lt;br /&gt;
&#039;&#039;&#039;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 &#039;&#039;per spin&#039;&#039; 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?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As before but not 5 repeats and do for 2x2, 4x4, 16x16 and 32x32. Only three repeats of each due to time constraints.&lt;br /&gt;
&lt;br /&gt;
It can be seen that the long range fluctuations become less significant as the lattice size increases. It appears that the 16x16 lattice is the smallest lattice in which the long range fluctuations can be obviously observed. A sample of the code use to plot the graphs shown below is shown.&lt;br /&gt;
&lt;br /&gt;
CODE from graphs&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Energy per Spin Graph&lt;br /&gt;
!Magnetisation per Spin Graph&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:evsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:evsT_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:Energyperspinvstemperature8x8afg216png.png|300px|thumb|center]]&lt;br /&gt;
|[[File:Magperspinvstemperature8x8afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:evsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:evsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|[[File:MvsT_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity: Tasks 15 and 16==&lt;br /&gt;
&#039;&#039;&#039;Task 15: 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;
&#039;&#039;&#039;(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;.)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
To begin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;U = \left\langle E\right\rangle&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The variance in &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; can be defined as the rate of change of &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt; undergoing thermal fluctuations. Thus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\mathrm{Var}[U] = - \frac{\partial U}{\partial \beta}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;\beta = \frac{1}{k_B T}&amp;lt;/math&amp;gt;. The heat capacity, &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt;, of the system is defined as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, by extension (and the product rule):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\partial U}{\partial T} = \frac{\partial U}{\partial \beta} \frac{\partial \beta}{\partial T}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and since we have:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;\frac{\partial U}{\partial \beta} = - \mathrm{Var}[U];\frac{\partial \beta}{\partial T} = - \frac{1}{k_B T^{2}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
we can conclude that:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
as required.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
UPDATE IMAGES.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;caption-side:bottom&amp;quot;&lt;br /&gt;
!Lattice Size&lt;br /&gt;
!Heat Capacity Graph&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
|2x2&lt;br /&gt;
|[[File:heatcapvstemp_2x2_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|4x4&lt;br /&gt;
|[[File:heatcapvstemp_4x4_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|8x8&lt;br /&gt;
|[[File:heatcapvstemp_8x8_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|16x16&lt;br /&gt;
|[[File:heatcapvstemp_16x16_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|32x32&lt;br /&gt;
|[[File:heatcapvstemp_32x32_afg216.png|300px|thumb|center]]&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It can be seen that the larger the lattice size, the sharper the heat capacity peak (which occurs at the Curie temperature) and the greater the error around the peak. Ideally more temperature values within the range would have been used to smooth the peaks somewhat, but time was restricted.&lt;br /&gt;
&lt;br /&gt;
The script used to calculate and plot heat capacity against temperature for the different lattice sizes is shown below. The factors used to convert the heat capacities form heat capacity per spin to heat capacity of the whole lattice are added into the code (and are simply the number of spins in the lattice, i.e. 2x2 has a factor of 4). The data used is averaged across three simulation runs of each size. Note that errors in the calculation of the squared energy and magnetisation (time restricted the amendment of the IsingLattice.py file and rerunning of the simulations) values when running the simulations are accounted for by the 90,000 (the number of cycles across which the average was taken) multiplication.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def heatcapacity(file,latticedimension):&lt;br /&gt;
    &#039;Plots a graph of heat capacity against temperature from a given file.&#039;&lt;br /&gt;
    Temps=temprange(file)&lt;br /&gt;
    E=avgEs(file)/(latticedimension**2)&lt;br /&gt;
    E2=(avgE2s(file)*90000)/(latticedimension**2 * latticedimension**2)&lt;br /&gt;
    VarE = E2 - (E**2)&lt;br /&gt;
    HeatCapacities = VarE / Temps**2&lt;br /&gt;
    return HeatCapacities&lt;br /&gt;
&lt;br /&gt;
twoav=(two1+two2+two3)/3&lt;br /&gt;
fourav=(four1+four2+four3)/3&lt;br /&gt;
eightav=(eight1+eight2+eight3)/3&lt;br /&gt;
sixtav=(sixt1+sixt2+sixt3)/3&lt;br /&gt;
thirav=(thir1+thir2+thir3)/3&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 2x2 Lattice&#039;)&lt;br /&gt;
plot(temprange(two1)[1:], heatcapacity(twoav, 2)[1:]*(2*2), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
.&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 32x32 Lattice&#039;)&lt;br /&gt;
plot(temprange(thir1)[1:], heatcapacity(thirav, 32)[1:]*(32*32), marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature: Tasks 17, 18, 19 and 20==&lt;br /&gt;
&#039;&#039;&#039;Task 17: 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 [https://github.com/niallj/ducking-avenger/tree/master/Ising here] if you are interested. 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;
The heat capacity calculated above in python of a 4x4 lattice is plotted against that given, calculated in C++, below in Figure X. Note that the heat capacities were originally calculated per spin, but, as above, here they represent those of the full lattices and the respective factors can be seen again in the code.&lt;br /&gt;
&lt;br /&gt;
[[File:heatcapvstempc++_4x4_afg216.png|300px|thumb|center|Figure X: Heat capacity of a 4x4 lattice as calculated in Python and in the given C++ data]]&lt;br /&gt;
&lt;br /&gt;
The plot code is shown here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FourCpl = loadtxt(&amp;quot;Cpl4x4.dat&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
xlabel(&#039;Temperature / K&#039;)&lt;br /&gt;
ylabel(&#039;Lattice Heat Capacity&#039;)&lt;br /&gt;
title(&#039;The Relationship Between Temperature and Heat Capacity of a 4x4 Lattice&#039;)&lt;br /&gt;
plot(temprange(four1)[1:], heatcapacity(four1, 4)[1:]*(4*4), marker=&amp;quot;o&amp;quot;, label=&amp;quot;Python Data&amp;quot;)&lt;br /&gt;
plot(temprange(FourCpl)[1:], FourCpl[1:, 5], marker=&amp;quot;.&amp;quot;, label=&#039;C++ Data&#039;)&lt;br /&gt;
legend(loc=&amp;quot;upper right&amp;quot;)&lt;br /&gt;
show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18: 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 &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;
plot c vs T, fit polynomial, for different polynomial degrees. Show script&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Same but only fit to peak; show script.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 20: 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.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&#039;center&#039;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find cmax for each file and show in table the corresponding T is the estimate of Tc for the lattice size; use scaling relation plot to extrapolate to T_C at infinite lattice size. Compare to literature and discuss errors.&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>Afg216</name></author>
	</entry>
</feed>